-
Notifications
You must be signed in to change notification settings - Fork 0
/
pae
338 lines (317 loc) · 13.6 KB
/
pae
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# pae version 0.8.2 https://github.com/cynic-net/pactivate
#
# pae provides pactivate-based commands similar to virtualenvwrapper.
# See the __pae_usage() function below for full documentation.
#
# Todo:
# - Bash completion of NAME argument.
# - `-p PATH` option to select Python interpreter.
#
[ -n "$BASH_VERSION" ] || { echo 1>&2 "source (.) this with Bash."; exit 9; }
# https://stackoverflow.com/a/28776166/107294
(return 0 2>/dev/null) \
|| { echo 1>&2 "source (.) pae with Bash."; exit 9; }
# You can change this in your environment if you want to download
# pactivate from a different source.
__pae_pactivate_url=https://raw.githubusercontent.com/cynic-net/pactivate/main/pactivate
__pae_usage() {
cat <<_____
Usage:
pae [-r] NAME [CMD] ... # run a command (default NAME) in a virtual env
pae -a NAME # activate a virtual env
pae -d # deactivate the current virtual env
pae -l # list virtual env dirs in virtual env home
pae [-p PATH] -c NAME [PKG ...] # create a virtual env using default
# Python or interpreter at PATH, and install any
# packages specified by PKG arguments.
pae -C NAME # as -c, but leave env activated after create
pae --rm NAME # remove a virtual env created by pae
# (this will not remove non-pae files)
pae -U # update the pactivate used by this function
pae -D # download latest version of pactivate
# to the current working directory
All commands also accept the following options:
-v Verbose mode; otherwise we try to run as quietly as possible.
Virtual environments are normally stored in the "virtual environment home,"
which is specified by the PAE_HOME environment variable. If that is not set
it falls back to WORKON_HOME for virtualenvwrapper compatibility, and
then ~/.pyvirtenv. However specifying a NAME parameter with a slash in it
will instead use the exact path specified. (Use ./NAME to use a directory
under the current working directory.)
This creates new virtual environments using pactivate, with the virtual
environment in the usual place in .build/virtualenv/ under the directory
or name given. However, for backward compatibility with virtualenvwrapper
it will also activate and deactivate any name/directory that directly
contains a virtual environment built with virtualenv.
_____
}
__pae_update_pactivate() {
# Should this be quieter unless -v is given?
local target_dir="$1"
echo "----- Downloading pactivate"
echo -n 'Current version: '
sed -n -e 2>/dev/null \
'/^# pactivate /s/ *https:.*//p' "$target_dir"/pactivate \
|| echo '(none)'
curl --fail --show-error '-#' ${quiet:+--silent} \
--create-dirs --output "$target_dir"/pactivate \
"$__pae_pactivate_url" \
|| { echo 1>&2 "pae: can't download pactivate"; return 22; }
echo -n 'Updated version: '
sed -n -e '/^# pactivate /s/ *https:.*//p' "$target_dir"/pactivate \
|| echo '(none)'
}
pae() {
local pae_home quiet action vedir bindir bincmd
# $pae_home: default virtual environments location
if [[ -v PAE_HOME ]]; then pae_home="$PAE_HOME"
elif [[ -v WORKON_HOME ]];then pae_home="$WORKON_HOME"
else pae_home=~/.pyvirtenv
fi
# $action: parse command line arguments
quiet=-q
action=run
pinterp=
while [[ ${#@} -gt 0 ]]; do case "$1" in
-a) shift; action=activate;;
-c) shift; action=create; local activate_after_create=false;;
-C) shift; action=create; local activate_after_create=true;;
-p) shift; pinterp="$1"; shift;;
-d) shift; action=deactivate;;
-h) __pae_usage; return 0;;
-l) shift;
ls --ignore pactivate "$pae_home"
return 0
;;
-r) shift; action=run;;
--rm) shift; action=remove;;
-U) __pae_update_pactivate "$pae_home"; return 0;;
-D) __pae_update_pactivate .; return 0;;
-v) shift; quiet='';;
-*) echo 1>&2 "pae: bad option: $1 (-h for help)"; return 2
;;
*) break;;
esac; done
if [[ ${#@} -eq 0 ]]; then
[[ $action == deactivate ]] || {
echo 1>&2 "pae: missing parameter (-h for help)"; return 2; }
elif [[ ${#@} -ne 1 && $action != run && $action != create ]]; then
echo 1>&2 "pae: too many parameters:" "$@"; return 2
else
if [[ $1 != ${1##*/} ]]; then # If there's a slash in the name,
vedir="$1" # we've been given a path.
elif [[ $1 == . ]]; then # Or if it's just `.`,
vedir=. # it's also a path.
else # Otherwise it's a name.
[[ $1 == .* || $1 == .. ]] && {
echo 1>&2 "pae: bad virtualenv name: $1"
return 2
}
vedir="$pae_home/$1"
fi
shift # remove last param
# While scripts under the virtualenv bin/ don't care about this,
# the bin/python binary there gets confused by adjacent slashes
# in its path (e.g., `bin//python`) and will not add the
# virtualenv's libraries to sys.path. To avoid this problem, we
# replace all adjacent slashes in $vedir with a single slash, and
# also remove any trailing slashes.
vedir=$(echo "$vedir" | sed -e 's,//*,/,g' -e 's,/$,,')
fi
case $action in
run)
# $bindir: confirm virtual env exists and find its bin dir.
if [[ -r $vedir/.build/virtualenv/bin/activate ]]; then
bindir="$vedir/.build/virtualenv/bin"
elif [[ -r $vedir/bin/activate ]]; then
# Backwards compatibility with virtualenvwrapper.
bindir="$vedir/bin"
else
echo 1>&2 "pae: $vedir does not appear to be a virtual env"
return 1
fi
# Find and run the command.
if [[ ${#@} -ge 1 && -x "$bindir/$1" ]]; then
# First arg is an exectuable in bin
bincmd="$1"
shift
else
# Assume that virtual env name is also command name.
bincmd="$(basename "$vedir")"
fi
[[ -z $quiet ]] \
&& echo "pae: running $bincmd from $bindir"
"$bindir/$bincmd" "$@"
;;
activate)
if [[ -r $vedir/.build/virtualenv/bin/activate ]]; then
. "$pae_home/pactivate" $quiet -B "$vedir"
elif [[ -r $vedir/bin/activate ]]; then
. "$vedir/bin/activate" # compatibility mode
else
echo 1>&2 "pae: $vedir does not appear to be a virtual env"
return 1
fi
;;
deactivate)
if [[ $(type -t deactivate) == function ]]; then
deactivate
else
echo 1>&2 "pae: no deactivate(); not in a virtual environment?"
return 1
fi
;;
create)
[[ -e $vedir/.build/virtualenv/bin/activate \
|| -e $vedir/bin/activate ]] \
&& {
echo 1>&2 "pae: virtual environment already exists in $vedir"
return 3
}
[[ -r "$pae_home/pactivate" ]] || __pae_update_pactivate "$pae_home"
mkdir -p "$vedir"
if [[ -n $pinterp ]]; then # alternate Python interpreter provided
echo "Python interpreter: $pinterp"
$pinterp -c 'import sys; print(sys.version)' || {
echo 1>&2 "pae: bad python interpreter: $pinterp"
return 2
}
(cd "$vedir" && ln -s "$(type -p "$pinterp")" .python)
fi
cat >"$vedir/README" <<_____
.build/virtualenv/ is a pactivate environment created by pae.
For more information, see https://github.com/cynic-net/pactivate
_____
for i in "$@"; do
echo "$i" >>"$vedir/requirements.txt"
done
if $activate_after_create; then
. "$pae_home/pactivate" $quiet -B "$vedir"
else
(. "$pae_home/pactivate" $quiet -B "$vedir")
fi
;;
remove)
# NOTE: This works only with pae-fromat environments; it's not
# worth trying to support this for virtualenvwrapper environments.
if ! [[ -e $vedir/.build/virtualenv/bin/activate ]]; then
echo 1>&2 "pae:" \
"$vedir does not appear to have a pae virtual env"
return 1
fi
# Remove README only if it appears to be one created by pae.
if [[ 0 -eq $(grep -s -c -E -v \
'is a pactivate environment created by pae|For more information, see https://github.com/cynic-net/pactivate' \
"$vedir"/README
) ]]; then
rm -f "$vedir"/README
fi
# Remove the virtualenv and its bootstrap.
rm -rf "$vedir"/.build/{bootstrap/pactivate*,virtualenv}
# Remove any .python interpreter link and requirements.txt we may
# have created. (Probably we created; nobody else should have!)
rm -f "$vedir"/.python "$vedir"/requirements.txt
# Remove any requirements.txt we may have created
# Attempt to remove remaining directories; fail quietly
# if they're not empty as we handle that later.
rmdir >/dev/null 2>&1 "$vedir"/.build/bootstrap "$vedir"/.build \
|| true
rmdir "$vedir" || {
echo 1>&2 'Warning: non-pae files not removed.'
return 0
}
;;
*)
echo 1>&2 "pae: internal error: bad action '$action'."
return 9
;;
esac
}
__pae_debug() {
echo >>~/tmp/pae.debug "$@"
}
__pae_complete() {
local debug=':'
#local debug='__pae_debug' # uncomment to enable debugging
$debug
$debug "1=$1 2=$2 3=$3 COMP_CWORD=$COMP_CWORD"
$debug "COMP_WORDS=${COMP_WORDS[@]}"
# $pae_home: default virtual environments location
local pae_home
if [[ -v PAE_HOME ]]; then pae_home="$PAE_HOME"
elif [[ -v WORKON_HOME ]]; then pae_home="$WORKON_HOME"
else pae_home=~/.pyvirtenv
fi
local command="$1" word="$2" prevword="$3"
local vepath vebase i
# Determine commpletion state from previous words on command line.
# =0: completing options or virtual env name
# =1: we have virtual env name; completing executable
# ≥2: we have executable; completing options to it
local cstate=0 # completing options or virtual env name
for i in $(seq 0 $(($COMP_CWORD - 1)) ); do
$debug "state $cstate check $i: ${COMP_WORDS[i]}"
# If we're at the start; stay in state 0.
[[ $i -eq 0 ]] && continue # at start of command
[[ ${COMP_WORDS[$i]} =~ ^- ]] && continue # going through options
# found a non-option; must be virtual env name or path
[[ -z $vepath ]] && {
# the first one we come across is virtual env name or path
$debug "stripped=${COMP_WORDS[$i]#*/}"
if [[ ${COMP_WORDS[$i]#*/} != ${COMP_WORDS[$i]} ]]; then
vepath="${COMP_WORDS[$i]}" # has a slash; must be path
else
vepath="$pae_home/${COMP_WORDS[$i]}"
fi
$debug "vepath=$vepath"
}
cstate=$(($cstate + 1)) # passed a non-option
done
$debug "state $cstate"
if [[ $cstate -eq 0 && $word =~ ^- ]]; then
# pae option.
mapfile -t COMPREPLY < <(compgen -W "-r -a -d -l -c --rm -v" -- "$word")
elif [[ $cstate -eq 0 ]]; then
# Virtual env name or path
COMPREPLY=()
$debug "glob:" "$pae_home/$2"*
if [[ ${word#*/} == $word ]]; then
# No slashes; complete from $pae_home
for vepath in "$pae_home/$word"*; do
vebase="$(basename "$vepath")"
[[ $vebase == pactivate ]] && continue
COMPREPLY+=("$vebase")
done
else
# word has slashes; standard filename completion
for i in $(compgen -G "${word}*"); do
COMPREPLY+=("$i")
done
fi
elif [[ $cstate -eq 1 ]]; then
$debug "cstate=2 vepath=$vepath"
local vebin
for vebin in "$vepath/.build/virtualenv/bin" "$vepath/bin" ""; do
$debug "vebin check: $vebin"
[[ -r $vebin/activate ]] && break
done
$debug "vebin=$vebin"
if [[ -z $vebin ]]; then
# not a virtual env
unset COMPREPLY
else
COMPREPLY=()
for i in "$vebin/"*; do
[[ ! -x $i && ! -L $i ]] && continue # not executable
i="$(basename "$i")"
[[ $i == ${word}* ]] && COMPREPLY+=( "$(basename "$i")" )
done
fi
else
# TODO: re-use the comspec for the exectuable in virtualenv if
# available
unset COMPREPLY
fi
$debug "COMP_REPLY=${COMPREPLY[@]}"
}
complete -F __pae_complete pae