Last active
August 25, 2024 11:48
-
-
Save qexat/53c12bbef7731cabfbf4e788a2fa3d9a to your computer and use it in GitHub Desktop.
recreate-venv - a Python-oriented shell utility to recreate a fresh new virtual environment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# ===================== LICENSE ===================== # | |
# MIT License # | |
# # | |
# Copyright (c) 2024 Qexat # | |
# # | |
# Permission is hereby granted, free of charge, to # | |
# any person obtaining a copy of this software and # | |
# associated documentation files (the "Software"), to # | |
# deal in the Software without restriction, including # | |
# without limitation the rights to use, copy, modify, # | |
# merge, publish, distribute, sublicense, and/or sell # | |
# copies of the Software, and to permit persons to # | |
# whom the Software is furnished to do so, subject to # | |
# the following conditions: # | |
# # | |
# The above copyright notice and this permission # | |
# notice shall be included in all copies or # | |
# substantial portions of the Software. # | |
# # | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY # | |
# OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT # | |
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # | |
# FITNESS FOR A PARTICULAR PURPOSE AND # | |
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # | |
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES # | |
# OR OTHER LIABILITY, WHETHER IN AN ACTION OF # | |
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF # | |
# OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # | |
# OTHER DEALINGS IN THE SOFTWARE. # | |
####################################################### | |
####################################################### | |
# USAGE ============================================= # | |
# recreate-venv .venv/ # | |
# # | |
# DEPENDENCIES ====================================== # | |
# - `python` # | |
# - `virtualenv` # | |
# # | |
# ERROR CODES ======================================= # | |
# 1: MISSING_ARGUMENT: the program expected an # | |
# argument, but received none. # | |
# 129: VENV_NOT_FOUND: the provided virtual # | |
# environment directory does not exist. # | |
# 130: VENV_SYMLINK: the provided virtual # | |
# environment is a symbolic link. # | |
# 131: PYTHONV_UNAVAILABLE; the retrieval of the # | |
# Python version failed. # | |
# 132: UNNECESSARY_SUDO: the program should NOT be # | |
# run as root. # | |
# 133: MISSING_DEPENDENCY: the program requires a # | |
# program to be on the path, but it could not # | |
# be found. # | |
# 254: INTERNAL_ERROR: the program triggered an # | |
# error on its own (bug). # | |
# # | |
# What it does -------------------------------------- # | |
# Equivalent to virtualenv --clear <path> # | |
# 1. Delete the current virtual environment # | |
# 2. Create a fresh one with the same Python version # | |
####################################################### | |
set -euo pipefail | |
# === EXIT CODES === # | |
EC_MISSING_ARGUMENT=1 | |
EC_VENV_NOT_FOUND=129 | |
EC_VENV_SYMLINK=130 | |
EC_PYTHONV_UNAVAILABLE=131 | |
EC_UNNECESSARY_SUDO=132 | |
EC_MISSING_DEPENDENCY=133 | |
EC_INTERNAL_ERROR=254 | |
# ================== # | |
####################################################### | |
# Print a pretty error to the standard error output. # | |
# # | |
# Arguments # | |
# --------- # | |
# message: the error message # | |
# # | |
# Errors # | |
# ------ # | |
# INTERNAL_ERROR: if no argument was provided. # | |
####################################################### | |
print_error() { | |
if [ "$#" -lt 1 ] | |
then | |
print_error "print_error: expected an argument" | |
exit 254 | |
fi | |
printf "\x1b[1;31mError\x1b[22;39m: %s\n" "$1" >&2 | |
} | |
####################################################### | |
# Print the usage of the program to the standard # | |
# error output. # | |
####################################################### | |
print_usage() { | |
LINE1="ββ [1mUsage[22m βββββββββββββββββββββββββββββββββββββββββββββββ" | |
LINE2="β [34mrecreate-venv[39m [3;36m<venv path>[23;39m β" | |
LINE3="ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
printf " %s\n" "$LINE1" "$LINE2" "$LINE3" >&2 | |
} | |
####################################################### | |
# Check whether the given program is available. # | |
# # | |
# Arguments # | |
# --------- # | |
# program: the program to check # | |
# # | |
# Errors # | |
# ------ # | |
# INTERNAL_ERROR: if the argument was missing. # | |
####################################################### | |
is_available() { | |
if [ "$#" -lt 1 ] | |
then | |
print_error "is_available: expected an argument" | |
exit $EC_INTERNAL_ERROR | |
fi | |
if command -v "$1" &> /dev/null | |
then | |
return 0 | |
else | |
return 1 | |
fi | |
} | |
####################################################### | |
# Check whether the given dependencies are available. # | |
# # | |
# Arguments # | |
# --------- # | |
# The dependencies to check # | |
# # | |
# Errors # | |
# ------ # | |
# INTERNAL_ERROR: if no argument was provided. # | |
# MISSING_DEPENDENCY: if a dependency is missing. # | |
####################################################### | |
check_dependencies() { | |
if [ "$#" -lt 1 ] | |
then | |
print_error "check_dependencies: expected at least one argument" | |
exit $EC_INTERNAL_ERROR | |
fi | |
for dependency in "$@" | |
do | |
if ! $(is_available "$dependency") | |
then | |
print_error "missing dependency: $dependency" | |
exit $EC_MISSING_DEPENDENCY | |
fi | |
done | |
} | |
####################################################### | |
# Get the current Python version and print it. # | |
# # | |
# Arguments # | |
# --------- # | |
# root (optional): the root directory from where to # | |
# find the Python binary # | |
####################################################### | |
get_python_version() { | |
"$1"/bin/python -c "import sys; print(*sys.version_info[:2], sep='')" | |
} | |
####################################################### | |
# Entry point of the program. # | |
# # | |
# Arguments # | |
# --------- # | |
# path: the path of the virtual environment to # | |
# recreate. # | |
# # | |
# Errors # | |
# ------ # | |
# UNNECESSARY_SUDO: if the program is run as sudo. # | |
# MISSING_ARGUMENT: if no argument was provided. # | |
# VENV_NOT_FOUND: if the venv path does not exist. # | |
# VENV_SYMLINK: if the venv path is a symbolic link. # | |
# PYTHONV_UNAVAILABLE: if the python version is # | |
# unavailable. # | |
####################################################### | |
main() { | |
# Make sure we don't run this script as administrator to avoid blunders. | |
if [ $(id -u) -eq 0 ] | |
then | |
print_error "do NOT run this script as administrator" | |
exit $EC_UNNECESSARY_SUDO | |
fi | |
# Make sure we are not missing any dependencies. | |
check_dependencies "python" "virtualenv" | |
# The virtual environment path must be provided. | |
if [ "$#" -lt 1 ] | |
then | |
print_usage | |
print_error "expected an argument" | |
exit $EC_MISSING_ARGUMENT | |
fi | |
VENV_PATH="$1" | |
# The virtual environment path must be a directory. | |
if [ ! -d "$VENV_PATH" ] | |
then | |
print_error "'$VENV_PATH' does not exist or is not a directory" | |
exit $EC_VENV_NOT_FOUND | |
# and not a symbolic link. | |
elif [ -L "$VENV_PATH" ] | |
then | |
print_error "the virtual environment cannot be a symbolic link" | |
exit $EC_VENV_SYMLINK | |
fi | |
PYTHON_VERSION=$(get_python_version "$VENV_PATH") | |
# Probably never happens, but just in case. | |
if [ -z "$PYTHON_VERSION" ] | |
then | |
print_error "could not get the version of Python" | |
exit $EC_PYTHONV_UNAVAILABLE | |
fi | |
rm -rf "$VENV_PATH" | |
virtualenv "$VENV_PATH" "-ppython$PYTHON_VERSION" | |
} | |
main $@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment