-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathvenv-now
More file actions
executable file
·145 lines (124 loc) · 3.82 KB
/
venv-now
File metadata and controls
executable file
·145 lines (124 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/env bash
SOURCED=$([[ ${BASH_SOURCE[0]} != "$0" ]] && echo true || echo false)
if [[ $SOURCED == false ]]; then
set -euo pipefail
fi
return-or-exit() {
[[ $SOURCED == true ]] && return "$1" || exit "$1"
}
# Pure validation function - returns 0 if path is dangerous, 1 if safe
# Can be tested safely without triggering any file operations
# Parameters:
# $1 - venv_dir: the raw directory argument
# $2 - resolved_dir: the resolved/normalized path
# $3 - current_dir: the current working directory
is_dangerous_venv_path() {
local venv_dir="$1"
local resolved_dir="$2"
local current_dir="$3"
[[ -z "$venv_dir" || "$venv_dir" == "." || "$venv_dir" == ".." ||
"$venv_dir" == "/" || "$venv_dir" == "~" ||
"$resolved_dir" == "$HOME" || "$resolved_dir" == "/" ||
"$resolved_dir" == "$current_dir" ]]
}
# When sourced with VENV_NOW_SOURCE_ONLY=1, only export functions (for testing)
# This allows tests to call is_dangerous_venv_path() without triggering file ops
if [[ "${VENV_NOW_SOURCE_ONLY:-}" == "1" ]]; then
return 0
fi
VENV_DIR=".venv"
REMOVE_EXISTING=true
SCRIPT_NAME=$(basename "$0")
usage() {
local exit_code=${1:-0}
cat <<EOF
Usage: $SCRIPT_NAME [DIRECTORY] [OPTIONS]
Create a new Python virtual environment in ./.venv by default. If the specified
directory already exists, it will be removed by default unless the --no-remove flag is
used. The virtual environment will be activated if the script is sourced.
Positional Arguments:
DIRECTORY The virtual environment directory name (default: .venv)
Options:
-n, --no-remove Do not remove an existing virtual environment
-h, --help Show this help message and exit
Examples:
. $SCRIPT_NAME
source $SCRIPT_NAME myenv --no-remove
$SCRIPT_NAME .venv -n
EOF
return-or-exit "$exit_code"
}
# Initialize empty positional argument
positional_arg_set=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-h | --help)
usage
;;
-n | --no-remove)
REMOVE_EXISTING=false
shift
;;
-*)
echo "Unknown option: $1" >&2
usage 1
;;
*)
if [ "$positional_arg_set" = false ]; then
VENV_DIR="$1"
positional_arg_set=true
else
echo "Error: Multiple directory arguments specified: '$VENV_DIR' and '$1'" >&2
usage 1
fi
shift
;;
esac
done
# Find python executable (prefer python3 for systems without python symlink)
PYTHON_CMD="python"
if ! command -v python &>/dev/null && command -v python3 &>/dev/null; then
PYTHON_CMD="python3"
fi
# Global safety check: refuse obviously dangerous targets
# Resolve and normalize the target virtual environment path safely,
# even if the directory does not exist yet.
RESOLVED_VENV_DIR="$(
"$PYTHON_CMD" - "$VENV_DIR" <<'EOF'
from pathlib import Path
import sys
# Path.resolve() resolves '..' and symlinks without requiring the
# final path component to exist (strict=False is the default in 3.6+).
print(Path(sys.argv[1]).resolve(strict=False))
EOF
)" || {
echo "Error: Failed to resolve path '$VENV_DIR'" >&2
return-or-exit 1
}
# Get current directory for comparison
CURRENT_DIR="$(pwd -P)"
if is_dangerous_venv_path "$VENV_DIR" "$RESOLVED_VENV_DIR" "$CURRENT_DIR"; then
echo "Error: Refusing to use dangerous path: '$VENV_DIR' (resolves to '$RESOLVED_VENV_DIR')" >&2
return-or-exit 1
fi
if $REMOVE_EXISTING && [ -d "$VENV_DIR" ]; then
echo "Removing existing virtual environment: $VENV_DIR"
rm -rf -- "$VENV_DIR"
fi
echo "Creating virtual environment in: $VENV_DIR"
"$PYTHON_CMD" -m venv "$VENV_DIR"
if [ -f "$VENV_DIR/bin/activate" ]; then
echo "Virtual environment created: $VENV_DIR"
else
echo "Error: Virtual environment creation failed!" >&2
return-or-exit 1
fi
if [[ $SOURCED == true ]]; then
# shellcheck disable=SC1091
{ . "$VENV_DIR/bin/activate" && echo "Virtual environment activated: $VENV_DIR"; } ||
{
echo "Error: Virtual environment activation failed!" >&2
return 1
}
fi