-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutil.elv
242 lines (221 loc) · 7.2 KB
/
util.elv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
use file
use flag
use os
use re
use str
# Reverse a sequence of values.
fn reverse {|@inputs|
if (== 0 (count $inputs)) { set inputs = [[(all)]] }
put $@inputs[(range (- (count $@inputs) 1) -1)]
}
# Filters a sequence of items and outputs those for whom the function outputs
# $true.
fn filter {|&out=$false func~ @inputs|
if $out {
each {|item| if (not (func $item)) { put $item } } $@inputs
} else {
each {|item| if (func $item) { put $item } } $@inputs
}
}
# Like ** but only returns regular files.
#
# Takes a sequence of filename extensions to limit the output to files with
# the specified extensions. Don't include the separating dot.
fn ff {|@ext|
fn filter {|p|
# Ignore .git directories.
if (re:match '(^|/)\.git(/|$)' $p) { return }
if (== 0 (count $ext)) {
# Emit all paths since no extensions were given.
put $p
} else {
# Only emit paths matching the wanted extensions.
each {|e|
if (re:match '\.'$e'$' $p) { put $p; return }
} $ext
}
}
put **[type:regular] |
each $filter~ |
order
}
fn gitfiles {|@args|
var @files = (git status --porcelain --short --untracked-files=all $@args |
each {|l| re:replace '^ *[^ ]* *' '' $l} | order &key={|k| str:to-lower $k })
if (eq [] $files) {
set @files = (git show --word-diff=porcelain --name-only --pretty=oneline $@args |
drop 1 | order &key={|k| str:to-lower $k } )
}
for f $files {
if (os:exists $f) {
put $f
}
}
}
fn machname {||
use platform
platform:hostname &strip-domain
}
# Simulate the POSIX `which` command. We do this for two reasons:
# 1) to avoid spawning an external command, and
# 2) on Windows the MSYS2 `which` command doesn't do what we want.
#
# Specifically, regarding point #2 above, the MSYS2 `which ls` command will
# output somthing like `/usr/bin/ls` where we want `c:\msys64\usr\bin\ls.exe`.
fn which {|@args|
var all = $false
var no-output = $false
var arg-specs = [
[&short=a &arg-optional=$false &arg-required=$false]
[&short=s &arg-optional=$false &arg-required=$false]
]
var flags args = (
flag:parse-getopt $args $arg-specs ^
&stop-after-double-dash=$true &stop-before-non-flag=$true &long-only=$false
)
for flag $flags {
if (eq $flag[spec][short] a) {
set all = $true
}
if (eq $flag[spec][short] s) {
set no-output = $true
}
}
if (== 0 (count $args)) {
fail 'You have to provide at least one external command name'
}
var not-found = $false
for arg $args {
try {
var result = (search-external $arg)
if (eq $no-output $false) {
put $result
}
} catch {
if (eq $no-output $false) {
fail 'External command '(printf "%q" $arg)' was not found'
} else {
set not-found = $true
}
}
}
if (eq $not-found $true) {
fail 'One or more commands were not found'
}
}
# -fenv-to-map converts the lines emitted by the e:env command into an Elvish
# map.
fn -fenv-from-json {||
each {|l|
var kv = [(str:split &max=2 = $l)]
if (!= 2 (count $kv)) {
# This should only happen if the value of an env var contains
# newlines. Not much we can do other than ignore those lines.
continue
}
put $kv
} | make-map
}
# -fenv-to-map converts the lines emitted by the e:env command into an Elvish
# map.
fn -fenv-from-env {||
each {|l|
var kv = [(str:split &max=2 = $l)]
if (!= 2 (count $kv)) {
# This can only happen if the value of an env var contains
# newlines. Not much we can do other than ignore those lines (and
# use an incorrect, truncated, value for the env var). If the user
# needs to handle this case they should install the `jq` utility.
continue
}
put $kv
} | make-map
}
# -fenv-update compares two env maps and applies the difference to the current
# environment. That is,
#
# a) env vars in the new env are added to the current env,
# b) env vars not in the new env are removed from the current env, and
# c) env vars whose value in the new env differs from the current env is
# back-propagated to the current env.
fn -fenv-update {|&dry-run=$false old-env new-env|
# We never want changes to these env vars propagated back into the current
# shell process. They should be silently ignored.
var ignored-env-vars = ['_' 'SHLVL' 'PWD' 'OLDPWD' 'XPC_SERVICE_NAME']
for key [(keys $old-env)] {
if (has-value $ignored-env-vars $key) {
continue
}
if (not (has-key $new-env $key)) {
if $dry-run {
echo 'Env var deleted: '$key'='(repr $old-env[$key])
} else {
unset-env $key
}
} elif (not (eq $old-env[$key] $new-env[$key])) {
if $dry-run {
echo 'Env var modified: '$key
echo ' was: '(repr $old-env[$key])
echo ' now: '(repr $new-env[$key])
} else {
set-env $key $new-env[$key]
}
}
}
for key [(keys $new-env)] {
if (has-value $ignored-env-vars $key) {
continue
}
if (not (has-key $old-env $key)) {
if $dry-run {
echo 'Env var added: '$key'='(repr $new-env[$key])
} else {
set-env $key $new-env[$key]
}
}
}
}
# fenv runs the script in your login shell (which is presumably a POSIX shell
# like Bash or Zsh) and propagates any changes to its environment back into
# the current Elvish process. This is useful for situations such as virtual
# environment initialization done by tools like `conda`, `pyenv` or `venv`
# which do not have native support for Elvish.
#
# This uses the `jq` command if available to correctly handle env vars whose
# value includes newlines. If `jq` isn't available it uses the `env` command
# and will not correctly handle env vars with embedded newlines.
fn fenv {|&dry-run=$false script|
var old-env
var new-env
var tmpf = (os:temp-file)
defer {
file:close $tmpf
os:remove $tmpf[name]
}
if (has-external jq) {
set old-env = (jq -n env | from-json)
$E:SHELL -ic $script'; jq -n env > '$tmpf[name];
set new-env = (from-json < $tmpf)
} else {
set old-env = (e:env | -fenv-from-env)
$E:SHELL -ic $script'; env > '$tmpf[name];
set new-env = (-fenv-from-env < $tmpf)
}
-fenv-update &dry-run=$dry-run $old-env $new-env
}
# Deactivate an Anaconda environment.
fn coff {||
fenv 'conda deactivate'
}
# Without an arg list the available Anaconda environments; otherwise, activate
# the named Anaconda environment.
fn con {|@args|
if (== 0 (count $args)) {
e:conda info --envs
return
}
if (> (count $args) 1) {
fail 'Too many args -- expected at most one conda environment name.'
}
fenv 'conda activate '$args[0]
}