Skip to content

Instantly share code, notes, and snippets.

@qexat
Last active August 25, 2024 11:48
Show Gist options
  • Save qexat/53c12bbef7731cabfbf4e788a2fa3d9a to your computer and use it in GitHub Desktop.
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
#!/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="β”Œβ”€ Usage ──────────────────────────────────────────────┐"
LINE2="β”‚ recreate-venv <venv path> β”‚"
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