|
2 | 2 | # |
3 | 3 | # Summary: Find virtual environments that use pyenv-managed versions. |
4 | 4 | # |
5 | | -# Usage: pyenv users [directory] |
| 5 | +# Usage: pyenv users [-r|--raw] [directory] |
| 6 | +# |
| 7 | +# -r/--raw Raw output strings as "<version>:<venv-path>" |
6 | 8 | # |
7 | 9 | # Scans [directory] for virtual environments whose `python` commands |
8 | 10 | # are symlinks back into a pyenv version. Default: current directory. |
9 | 11 |
|
10 | | -if [ -n "$1" ]; then |
| 12 | +set -e |
| 13 | + |
| 14 | +function collect_links () { |
| 15 | + # Collect all symlinks named python that point into $PYENV_ROOT |
11 | 16 | DIR="$1" |
12 | | -else |
| 17 | + local -n _links=$2 |
| 18 | + cmd="readlink -f '{}' | grep -q ${PYENV_ROOT}" |
| 19 | + unset i |
| 20 | + while IFS= read -r -d $'\0' file; do |
| 21 | + _links[i++]="$file" |
| 22 | + done < <(find -H "$DIR" -name "python" -type l -exec sh -c "$cmd" \; -print0) |
| 23 | +} |
| 24 | + |
| 25 | +function collect_pairs () { |
| 26 | + # Turn each link into a (version, venv) string pair |
| 27 | + local -n _links=$1 _versions=$2 _venvs=$3 |
| 28 | + |
| 29 | + # Regex to extract the pyenv version from the target string. The |
| 30 | + # second group consumes the actual binary (`python`, `pypy3`, etc) |
| 31 | + regex="${PYENV_ROOT}/versions/(.+)/bin/(.+)" |
| 32 | + |
| 33 | + unset i |
| 34 | + for link in "${_links[@]}"; do |
| 35 | + # `$link` is the `python` symlink, and `$target` is its target. |
| 36 | + linkpath=$(realpath -s "$link") |
| 37 | + target=$(readlink -f "$link") |
| 38 | + [[ "$target" =~ $regex ]] |
| 39 | + version="${BASH_REMATCH[1]}" |
| 40 | + # Only capture links outside PYENV_ROOT or inside pyenv-virtualenv venvs |
| 41 | + if grep -v -q "$PYENV_ROOT" <<< "$linkpath" || \ |
| 42 | + grep -q "$PYENV_ROOT/versions/$version/envs" <<< "$linkpath" |
| 43 | + then |
| 44 | + _versions[i]="$version" |
| 45 | + _venvs[i++]="${link%/bin/python}" |
| 46 | + fi |
| 47 | + done |
| 48 | +} |
| 49 | + |
| 50 | +function print_pairs () { |
| 51 | + # Print each (version, venv) pair |
| 52 | + local -n _versions=$1 _venvs=$2 |
| 53 | + local -i K=${#_versions[@]} width=0 maxwidth=0 |
| 54 | + |
| 55 | + # Use the longest $version to setup the columns |
| 56 | + for (( k=0; k < K; k++ )); do |
| 57 | + width=${#_versions[k]} |
| 58 | + if (( width > maxwidth )); then maxwidth=$width; fi |
| 59 | + done |
| 60 | + |
| 61 | + for (( k=0; k < K; k++ )); do |
| 62 | + if [ -z "$RAW" ]; then |
| 63 | + printf "%-*s %s\n" "$maxwidth" "${_versions[$k]}" "${_venvs[k]}" |
| 64 | + else |
| 65 | + echo "${_versions[$k]}":"${_venvs[$k]}" |
| 66 | + fi |
| 67 | + done | sort |
| 68 | +} |
| 69 | + |
| 70 | +parse_options() { |
| 71 | + # Parse the command line options. Taken from `pyenv-virtualenv` |
| 72 | + OPTIONS=() |
| 73 | + ARGUMENTS=() |
| 74 | + local arg option index |
| 75 | + |
| 76 | + for arg in "$@"; do |
| 77 | + if [ "${arg:0:1}" = "-" ]; then |
| 78 | + if [ "${arg:1:1}" = "-" ]; then |
| 79 | + OPTIONS[${#OPTIONS[*]}]="${arg:2}" |
| 80 | + else |
| 81 | + index=1 |
| 82 | + while option="${arg:$index:1}"; do |
| 83 | + [ -n "$option" ] || break |
| 84 | + OPTIONS[${#OPTIONS[*]}]="$option" |
| 85 | + index=$((index+1)) |
| 86 | + done |
| 87 | + fi |
| 88 | + else |
| 89 | + ARGUMENTS[${#ARGUMENTS[*]}]="$arg" |
| 90 | + fi |
| 91 | + done |
| 92 | +} |
| 93 | + |
| 94 | +if [ -z "$PYENV_ROOT" ]; then |
| 95 | + PYENV_ROOT=$(pyenv root) |
| 96 | +fi |
| 97 | + |
| 98 | +unset RAW |
| 99 | +parse_options "$@" |
| 100 | +for option in "${OPTIONS[@]}"; do |
| 101 | + case "$option" in |
| 102 | + "r" | "raw" ) |
| 103 | + RAW=true |
| 104 | + ;; |
| 105 | + "h" | "help" ) |
| 106 | + pyenv help users |
| 107 | + exit 0 |
| 108 | + ;; |
| 109 | + esac |
| 110 | +done |
| 111 | + |
| 112 | +if [[ "${#ARGUMENTS[@]}" == 0 ]]; then |
13 | 113 | DIR="$PYENV_DIR" |
| 114 | +elif [[ "${#ARGUMENTS[@]}" == 1 ]]; then |
| 115 | + DIR="${ARGUMENTS[0]}" |
14 | 116 | fi |
15 | 117 |
|
16 | | -cmd="readlink -f '{}' | grep -q ${PYENV_ROOT}" |
17 | | -unset links i |
18 | | -while IFS= read -r -d $'\0' file; do |
19 | | - links[i++]="$file" |
20 | | -done < <(find -H "$DIR" -name "python" -type l -exec sh -c "$cmd" \; -print0) |
| 118 | +# The `links` are the symlink pathnames, `versions` are pyenv version strings, |
| 119 | +# and `venvs` are venv pathnames. Using parallel arrays since arrays-of-arrays |
| 120 | +# are a pain in bash. Keeping versions and venvs separate avoids needing awk. |
| 121 | +declare -a links versions venvs |
| 122 | + |
| 123 | +collect_links "$DIR" links |
21 | 124 |
|
22 | 125 | # Exit if no relevant venvs were found |
23 | | -if [[ $i -eq 0 ]]; then |
| 126 | +if [ ${#links[@]} -eq 0 ]; then |
24 | 127 | exit 0 |
25 | 128 | fi |
26 | 129 |
|
27 | | -# Use columnar output if convenient. |
28 | | -if [ -n "$(command -v column)" ]; then |
29 | | - output="column -t -s ':'" |
30 | | -else |
31 | | - output="cat" |
32 | | -fi |
33 | | - |
34 | | -# Regex to extract the pyenv version from the target string. The |
35 | | -# second group consumes the actual binary (`python`, `pypy3`, etc) |
36 | | -regex="${PYENV_ROOT}/versions/(.+)/bin/(.+)" |
37 | | - |
38 | | -for link in "${links[@]}"; do |
39 | | - # `$link` is the `python` symlink, and `$target` is its target. |
40 | | - linkpath=$(realpath -s "$link") |
41 | | - target=$(readlink -f "$link") |
42 | | - [[ "$target" =~ $regex ]] |
43 | | - version="${BASH_REMATCH[1]}" |
44 | | - # Only capture links outside PYENV_ROOT or inside pyenv-virtualenv venvs |
45 | | - if grep -v -q "$PYENV_ROOT" <<< "$linkpath" || \ |
46 | | - grep -q "$PYENV_ROOT/versions/$version/envs" <<< "$linkpath" |
47 | | - then |
48 | | - echo "$version":"${link%/bin/python}" |
49 | | - fi |
50 | | -done | sort | $output |
| 130 | +collect_pairs links versions venvs |
| 131 | +print_pairs versions venvs |
0 commit comments