Skip to content

Instantly share code, notes, and snippets.

@courtc
Last active March 23, 2023 20:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save courtc/f01ddfe6798f9e6a5ae3 to your computer and use it in GitHub Desktop.
Save courtc/f01ddfe6798f9e6a5ae3 to your computer and use it in GitHub Desktop.
Virtualenv for Python: Yuck
#!/bin/bash -e
#
# Stay off my lawn
#
# I refuse to source scripts, so this is my way of dealing with virtualenv.
#
# Basically the idea here is that I mess up my own personal environment so that
# this script runs in place of python. With that I load the proper environment
# based on the python script being run. Environment mapping is based on the
# canonical file path of each script.
#
# SETUP:
# cp $0 ~/bin/vpy
# (cd ~/bin; for k in {vpy,python}{2,2.7,3,3.7}; do
# ln -s vpy "$k"
# done)
#
# USE:
# vpy new myapp # or e.g. vpy2.7 if you want python2.7 specifically
# vpy add myapp myapp.py
# vpy pip myapp install mydep # or: vpy run myapp pip install mydep
# ./myapp.py
#
# ALTERNATIVES:
# - vex (https://github.com/sashahart/vex)
# - virtualenvwrapper (http://virtualenvwrapper.readthedocs.org/en/latest/)
# - capn (https://github.com/dustinlacewell/capn)
# - pew (https://github.com/berdario/pew)
# - inve (https://gist.github.com/datagrok/2199506)
me="$(readlink -f "$0")"
bn="$(basename "$me")"
name="$(basename "$0")"
default_version="3"
venv_base="$HOME/.virtualenv"
venv_env="$venv_base/environments"
venv_cfg="$venv_base/config"
version="$(echo "$name" | tr -d "[a-zA-Z-]")"
[ -d "$venv_base" ] || mkdir -p "$venv_base"
[ -e "$venv_cfg" ] || touch "$venv_cfg"
usage() {
echo "Usage: $bn <command> [OPTIONS]"
echo ""
echo " Commands:"
echo " - new <env> create a new environment named <env>"
echo " - del <env> delete existing environment named <env>"
echo " - add <env> <prog> add program to environment named <env>"
echo " - find <prog> locate environment where program runs (if any)"
echo " - pip <env> <cmd...> run pip in existing environment named <env>"
echo " - run <env> <cmd...> run command in existing environment named <env>"
exit 1
}
# we keep track of a file's abs. path by hashing it; that way we don't
# need to deal with special characters when storing it
vpy_hash() {
readlink -f "$1" | sha1sum | cut -f1 -d' '
}
vpy_lookup() {
sed -n '/^'"$(vpy_hash "$1")"'/s/^[^\s]\+\s\+//p' "$venv_cfg"
}
vpy_delete() {
sed -i '/^[0-9a-f]* '"$1"'$/d' "$venv_cfg"
}
vpy_add() {
key="$1"
env="$2"
sed -i '/^'"$key"'\s/d' "$venv_cfg"
echo "$key $env" >> "$venv_cfg"
}
_vpy() {
[ -z "$version" ] && version="$default_version"
#[[ "$version" =~ [0-9].[0-9] ]] && version="-$version"
[ $# -lt 2 ] && usage
[ -z "$2" ] && usage
env="$2"
if [ "$1" == "new" ]; then
mkdir -p "$venv_env/$env"
exec "virtualenv${version:0:1}" --always-copy "$venv_env/$env"
elif [ "$1" == "del" ]; then
vpy_delete "$2"
exec rm -rf "$venv_env/$env"
elif [ "$1" == "add" ]; then
[ $# -lt 3 ] && usage
vpy_add "$(vpy_hash "$3")" "$env"
elif [ "$1" == "find" ]; then
vpy_lookup "$2"
elif [ "$1" == "hash" ]; then
vpy_hash "$2"
elif [ "$1" == "pip" ]; then
shift; shift
. "$venv_env/$env/bin/activate"
exec pip "$@"
elif [ "$1" == "run" ]; then
shift; shift
. "$venv_env/$env/bin/activate"
exec "$@"
else
usage
fi
}
# finds the first file argument on a typical python command-line
firstfile() {
lastchar=0
for arg in "$@"; do
if [ "${arg:0:1}" == "-" ]; then
lastchar="${arg:1:2}"
continue
fi
case "$lastchar" in [cWmX]) discard=1 ;; *) discard=0 ;; esac
lastchar=0
[ $discard -eq 1 ] && continue
echo "$arg"
return
done
}
if [[ "$name" =~ $bn[0-9.]* ]]; then
_vpy "$@"
elif [[ "$name" =~ python[0-9.]* ]]; then
file="$(firstfile "$@")"
[ -z "$file" ] || env="$(vpy_lookup "$file")"
[ -z "$env" ] || . "$venv_env/$env/bin/activate"
for ver in $(which -a "python$version"); do
[ "$(readlink -f "$ver")" != "$me" ] && exec "$ver" "$@"
done
echo "Unable to find suitable python version matching \"python$version\""
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment