Skip to content

Commit c005853

Browse files
authored
Merge pull request #1 from pfheatwole/work
Eliminate dependency on `column` and add raw output
2 parents 6e38e32 + fb86cb0 commit c005853

File tree

2 files changed

+112
-48
lines changed

2 files changed

+112
-48
lines changed

README.rst

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,17 @@ virtual environments, regardless of how they were created (``python -m venv
1414
Prerequisites
1515
-------------
1616

17-
Requires ``find`` from GNU ``findutils`` to collect a list of candidates.
17+
* GNU coreutils (for ``readlink`` and ``realpath``)
1818

19-
Uses ``column`` from the ``util-linux`` package (if available) for
20-
pretty-printing the output. If ``column`` is not available, the output is the
21-
simple form ``<version>:<venv path>``.
19+
* GNU findutils (for ``find``)
2220

2321

2422
Installation
2523
------------
2624

27-
Verify that ``$PYENV_ROOT`` has been configured:
28-
29-
.. code-block:: bash
30-
31-
$ echo $PYENV_ROOT
32-
33-
Install the plugin:
34-
3525
.. code-block:: bash
3626
37-
$ git clone https://github.com/pyenv/pyenv-users.git "$PYENV_ROOT"/plugins/pyenv-users
27+
$ git clone https://github.com/pyenv/pyenv-users.git "$(pyenv root)/plugins/pyenv-users"
3828
3929
4030
Usage
@@ -52,20 +42,34 @@ search your home directory:
5242
3.8.6 /home/peter/.cache/pypoetry/virtualenvs/my_project-KM_3YcvM-py3.8
5343
pypy3.6-7.3.1 /home/peter/work/venvs/example1
5444
45+
For scripting, use the ``--raw`` option to output a list of ``:`` separated
46+
items:
5547

56-
Disclaimer
57-
----------
48+
.. code-block:: bash
5849
59-
I'm not a script writer so it's probably a bit crude. It's not blazingly fast,
60-
since it uses a brute force scan, but on my system it only takes 3 seconds,
61-
and I like the simplicity.
50+
$ pyenv users --raw ~
51+
3.7.9:/home/peter/.cache/pypoetry/virtualenvs/my_project-KM_3YcvM-py3.7
52+
3.7.9:/home/peter/work/venvs/long name with spaces
53+
3.8.6:/home/peter/.cache/pypoetry/virtualenvs/my_project-KM_3YcvM-py3.8
54+
pypy3.6-7.3.1:/home/peter/work/venvs/example1
6255
63-
I've been using it on Fedora 33 with no issues, but it could use more testing.
64-
In particular, users on MacOS and Windows will probably prefer the addition of
65-
a pretty-printing solution that doesn't rely on ``column``.
56+
For example, to get a list of all versions linked to a virtual environment:
57+
58+
.. code-block:: bash
59+
60+
$ pyenv users --raw ~ | cut -d: -f1 | uniq
61+
3.7.9
62+
3.8.6
63+
pypy3.6-7.3.1
64+
65+
66+
Disclaimer
67+
----------
6668

67-
Also, I seem to recall that not all versions of ``column`` support the ``-s``
68-
parameter for setting the separator. I haven't had time to research that.
69+
The plugin doesn't maintain a list of environments; it generates the list each
70+
run by performing a brute force scan of the directory for symlinks named
71+
`python`. This does mean it's not blazingly fast, but its simplicity provides
72+
state-free reliability, and in practice ``find`` only takes a few seconds.
6973

70-
Tested with ``bash v5.0.17(1)``, ``find v4.7.0``, and ``column v2.36.1`` on
71-
Fedora 33.
74+
Also, it could really use more testing; so far I've been using it on Fedora 33
75+
with no issues. Tested with ``bash v5.0.17(1)`` and ``find v4.7.0``.

bin/pyenv-users

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,86 @@
22
#
33
# Summary: Find virtual environments that use pyenv-managed versions.
44
#
5-
# Usage: pyenv users [directory]
5+
# Usage: pyenv users [-r|--raw] [directory]
6+
#
7+
# -r/--raw Raw output strings as "<version>:<venv-path>"
68
#
79
# Scans [directory] for virtual environments whose `python` commands
810
# are symlinks back into a pyenv version. Default: current directory.
911

10-
if [ -n "$1" ]; then
11-
DIR="$1"
12-
else
12+
set -e
13+
14+
parse_options() {
15+
# Parse the command line options. Taken from `pyenv-virtualenv`
16+
OPTIONS=()
17+
ARGUMENTS=()
18+
local arg option index
19+
20+
for arg in "$@"; do
21+
if [ "${arg:0:1}" = "-" ]; then
22+
if [ "${arg:1:1}" = "-" ]; then
23+
OPTIONS[${#OPTIONS[*]}]="${arg:2}"
24+
else
25+
index=1
26+
while option="${arg:$index:1}"; do
27+
[ -n "$option" ] || break
28+
OPTIONS[${#OPTIONS[*]}]="$option"
29+
index=$((index+1))
30+
done
31+
fi
32+
else
33+
ARGUMENTS[${#ARGUMENTS[*]}]="$arg"
34+
fi
35+
done
36+
}
37+
38+
unset RAW
39+
parse_options "$@"
40+
for option in "${OPTIONS[@]}"; do
41+
case "$option" in
42+
"r" | "raw" )
43+
RAW=true
44+
;;
45+
"h" | "help" )
46+
pyenv help users
47+
exit 0
48+
;;
49+
esac
50+
done
51+
52+
if [[ "${#ARGUMENTS[@]}" == 0 ]]; then
1353
DIR="$PYENV_DIR"
54+
elif [[ "${#ARGUMENTS[@]}" == 1 ]]; then
55+
DIR="${ARGUMENTS[0]}"
56+
else
57+
echo -e "\nToo many directory arguments.\n"
58+
pyenv help users
59+
exit 1
1460
fi
1561

62+
# ----------------------------------------------------------------------------
63+
# Finished parsing the arguments. Begin the actual functionality.
64+
65+
# The `links` are the symlink pathnames, `versions` are pyenv version strings,
66+
# and `venvs` are venv pathnames. Using parallel arrays since arrays-of-arrays
67+
# are a pain in bash. Keeping versions and venvs separate avoids needing awk.
68+
declare -a links versions venvs
69+
70+
if [ -z "$PYENV_ROOT" ]; then
71+
PYENV_ROOT=$(pyenv root)
72+
fi
73+
74+
# Collect all symlinks named `python` that point into $PYENV_ROOT
1675
cmd="readlink -f '{}' | grep -q ${PYENV_ROOT}"
17-
unset links i
76+
unset i
1877
while IFS= read -r -d $'\0' file; do
1978
links[i++]="$file"
2079
done < <(find -H "$DIR" -name "python" -type l -exec sh -c "$cmd" \; -print0)
2180

22-
# Exit if no relevant venvs were found
23-
if [[ $i -eq 0 ]]; then
24-
exit 0
25-
fi
26-
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)
81+
# Turn each link into a (version, venv) string pair
3682
regex="${PYENV_ROOT}/versions/(.+)/bin/(.+)"
37-
83+
unset i
3884
for link in "${links[@]}"; do
39-
# `$link` is the `python` symlink, and `$target` is its target.
4085
linkpath=$(realpath -s "$link")
4186
target=$(readlink -f "$link")
4287
[[ "$target" =~ $regex ]]
@@ -45,6 +90,21 @@ for link in "${links[@]}"; do
4590
if grep -v -q "$PYENV_ROOT" <<< "$linkpath" || \
4691
grep -q "$PYENV_ROOT/versions/$version/envs" <<< "$linkpath"
4792
then
48-
echo "$version":"${link%/bin/python}"
93+
versions[i]="$version"
94+
venvs[i++]="${link%/bin/python}"
95+
fi
96+
done
97+
98+
# Print each (version, venv) pair
99+
declare -i K=${#versions[@]} width=0 maxwidth=0
100+
for (( k=0; k < K; k++ )); do
101+
width=${#versions[k]}
102+
if (( width > maxwidth )); then maxwidth=$width; fi
103+
done
104+
for (( k=0; k < K; k++ )); do
105+
if [ -z "$RAW" ]; then
106+
printf "%-*s %s\n" "$maxwidth" "${versions[$k]}" "${venvs[k]}"
107+
else
108+
echo "${versions[$k]}":"${venvs[$k]}"
49109
fi
50-
done | sort | $output
110+
done | sort

0 commit comments

Comments
 (0)