|
| 1 | +# ---------------------------------------------------------------- |
| 2 | +# Enhanced direnvrc - replaces virtualenvwrapper functionality |
| 3 | +# ---------------------------------------------------------------- |
| 4 | + |
| 5 | +# Set PROJECT_HOME if not already set |
| 6 | +export PROJECT_HOME="${PROJECT_HOME:-${HOME}/Projects}" |
| 7 | + |
| 8 | +# Ensure PROJECT_HOME directory exists |
| 9 | +if [[ ! -d "${PROJECT_HOME}" ]]; then |
| 10 | + mkdir -p "${PROJECT_HOME}" |
| 11 | +fi |
| 12 | + |
| 13 | +# Auto-detect and activate virtualenv based on project structure |
| 14 | +auto_virtualenv() { |
| 15 | + # Check for common Python project indicators |
| 16 | + if [[ -f "pyproject.toml" || -f "requirements.txt" || -f "setup.py" || -f "Pipfile" ]]; then |
| 17 | + local project_name="$(basename "$(pwd)")" |
| 18 | + local venv_candidates=( |
| 19 | + "venv" |
| 20 | + ".venv" |
| 21 | + "env" |
| 22 | + ".env" |
| 23 | + "${HOME}/.virtualenvs/${project_name}" |
| 24 | + ) |
| 25 | + |
| 26 | + for venv_dir in "${venv_candidates[@]}"; do |
| 27 | + if [[ -d "$venv_dir" && -f "$venv_dir/bin/activate" ]]; then |
| 28 | + layout_python_venv python3 "$venv_dir" |
| 29 | + return 0 |
| 30 | + fi |
| 31 | + done |
| 32 | + |
| 33 | + # If no venv found but we have project indicators, offer to create one |
| 34 | + if command -v uvx >/dev/null 2>&1; then |
| 35 | + uvx --quiet rich --print "[yellow]Python project detected but no virtual environment found[/yellow]" |
| 36 | + uvx --quiet rich --print "[blue]Consider running[/blue]: layout_python_venv python3 venv" |
| 37 | + fi |
| 38 | + fi |
| 39 | +} |
| 40 | + |
| 41 | +# Enhanced layout_python with project detection |
| 42 | +layout_python() { |
| 43 | + local python_exe="${1:-python3}" |
| 44 | + local venv_dir="${2:-venv}" |
| 45 | + |
| 46 | + # If we're in a project directory under PROJECT_HOME, use project-specific venv |
| 47 | + local current_project="" |
| 48 | + if [[ "$(pwd)" == "${PROJECT_HOME}"/* ]]; then |
| 49 | + current_project="$(basename "$(pwd)")" |
| 50 | + venv_dir="${HOME}/.virtualenvs/${current_project}" |
| 51 | + fi |
| 52 | + |
| 53 | + layout_python_venv "$python_exe" "$venv_dir" |
| 54 | +} |
| 55 | + |
| 56 | +# Create and activate a Python virtual environment |
| 57 | +layout_python_venv() { |
| 58 | + local python_exe="${1:-python3}" |
| 59 | + local venv_dir="${2:-venv}" |
| 60 | + |
| 61 | + # Create venv if it doesn't exist |
| 62 | + if [[ ! -d "$venv_dir" ]]; then |
| 63 | + log_status "Creating virtual environment: $venv_dir" |
| 64 | + "$python_exe" -m venv "$venv_dir" |
| 65 | + |
| 66 | + # Upgrade pip and install uv (matches your postmkvirtualenv hook) |
| 67 | + "$venv_dir/bin/python" -m pip install --upgrade pip uv |
| 68 | + |
| 69 | + # Rich output (matches your virtualenvwrapper style) |
| 70 | + if command -v uvx >/dev/null 2>&1; then |
| 71 | + uvx --quiet rich --print "[green]Virtual environment created[/green]: $venv_dir" |
| 72 | + fi |
| 73 | + fi |
| 74 | + |
| 75 | + # Activate the virtual environment |
| 76 | + source "$venv_dir/bin/activate" |
| 77 | + export VIRTUAL_ENV="$(pwd)/$venv_dir" |
| 78 | + PATH_add "$venv_dir/bin" |
| 79 | + |
| 80 | + # Set environment variables (matches your postactivate hook) |
| 81 | + export VIRTUAL_ENV_NAME="$(basename "$VIRTUAL_ENV")" |
| 82 | + |
| 83 | + # Rich output for activation |
| 84 | + if command -v uvx >/dev/null 2>&1; then |
| 85 | + uvx --quiet rich --print "[green]Virtual environment activated[/green]: $VIRTUAL_ENV_NAME" |
| 86 | + fi |
| 87 | +} |
| 88 | + |
| 89 | +# List all virtual environments (replaces lsvirtualenv) |
| 90 | +lsvirtualenv() { |
| 91 | + local venvs_dir="${HOME}/.virtualenvs" |
| 92 | + |
| 93 | + if [[ ! -d "$venvs_dir" ]]; then |
| 94 | + echo "No virtual environments found" |
| 95 | + return 0 |
| 96 | + fi |
| 97 | + |
| 98 | + echo "Available virtual environments:" |
| 99 | + for venv in "$venvs_dir"/*; do |
| 100 | + if [[ -d "$venv" && -f "$venv/bin/python" ]]; then |
| 101 | + local venv_name="$(basename "$venv")" |
| 102 | + local python_version="$("$venv/bin/python" --version 2>/dev/null)" |
| 103 | + echo " $venv_name ($python_version)" |
| 104 | + fi |
| 105 | + done |
| 106 | +} |
| 107 | + |
| 108 | +# Create a new project with virtualenv (replaces mkproject) |
| 109 | +mkproject() { |
| 110 | + local project_name="$1" |
| 111 | + local python_version="${2:-3.11}" |
| 112 | + |
| 113 | + if [[ -z "$project_name" ]]; then |
| 114 | + echo "Usage: mkproject <project_name> [python_version]" |
| 115 | + return 1 |
| 116 | + fi |
| 117 | + |
| 118 | + local project_dir="${PROJECT_HOME}/${project_name}" |
| 119 | + local venv_dir="${HOME}/.virtualenvs/${project_name}" |
| 120 | + |
| 121 | + # Rich output for pre-creation |
| 122 | + if command -v uvx >/dev/null 2>&1; then |
| 123 | + uvx --quiet rich --print "[yellow]Creating project[/yellow]: $project_name" |
| 124 | + uvx --quiet rich --print "[blue]PROJECT_HOME[/blue]: ${PROJECT_HOME}" |
| 125 | + fi |
| 126 | + |
| 127 | + # Create project directory |
| 128 | + mkdir -p "$project_dir" |
| 129 | + |
| 130 | + # Create virtual environment |
| 131 | + if command -v pyenv >/dev/null 2>&1; then |
| 132 | + pyenv virtualenv "$python_version" "$project_name" |
| 133 | + else |
| 134 | + python3 -m venv "$venv_dir" |
| 135 | + "$venv_dir/bin/python" -m pip install --upgrade pip uv |
| 136 | + fi |
| 137 | + |
| 138 | + # Rich output for post-creation |
| 139 | + if command -v uvx >/dev/null 2>&1; then |
| 140 | + uvx --quiet rich --print "[green]Project created[/green]: $project_dir" |
| 141 | + uvx --quiet rich --print "[green]Virtual environment created[/green]: $project_name" |
| 142 | + fi |
| 143 | + |
| 144 | + # Change to project directory |
| 145 | + cd "$project_dir" |
| 146 | +} |
| 147 | + |
| 148 | +# Remove a project and its virtualenv (replaces rmproject) |
| 149 | +rmproject() { |
| 150 | + local project_name="$1" |
| 151 | + |
| 152 | + if [[ -z "$project_name" ]]; then |
| 153 | + echo "Usage: rmproject <project_name>" |
| 154 | + return 1 |
| 155 | + fi |
| 156 | + |
| 157 | + local project_dir="${PROJECT_HOME}/${project_name}" |
| 158 | + local venv_dir="${HOME}/.virtualenvs/${project_name}" |
| 159 | + |
| 160 | + # Rich output for pre-removal |
| 161 | + if command -v uvx >/dev/null 2>&1; then |
| 162 | + uvx --quiet rich --print "[yellow]Removing project[/yellow]: $project_name" |
| 163 | + fi |
| 164 | + |
| 165 | + # Remove project directory |
| 166 | + if [[ -d "$project_dir" ]]; then |
| 167 | + rm -rf "$project_dir" |
| 168 | + fi |
| 169 | + |
| 170 | + # Remove virtual environment |
| 171 | + if [[ -d "$venv_dir" ]]; then |
| 172 | + rm -rf "$venv_dir" |
| 173 | + elif command -v pyenv >/dev/null 2>&1; then |
| 174 | + pyenv virtualenv-delete -f "$project_name" |
| 175 | + fi |
| 176 | + |
| 177 | + # Rich output for post-removal |
| 178 | + if command -v uvx >/dev/null 2>&1; then |
| 179 | + uvx --quiet rich --print "[green]Project removed[/green]: $project_name" |
| 180 | + fi |
| 181 | +} |
| 182 | + |
| 183 | +# Scan virtualenvs and display their python versions (from your virtualenv.justfile) |
| 184 | +scan_virtualenvs() { |
| 185 | + python3 -c " |
| 186 | +import subprocess |
| 187 | +from pathlib import Path |
| 188 | + |
| 189 | +folders = [folder for folder in Path(Path.home(), '.virtualenvs').glob('*/bin/python')] |
| 190 | +for command in folders: |
| 191 | + try: |
| 192 | + output = subprocess.run(f'{command} --version'.split(), capture_output=True, text=True) |
| 193 | + venv_name = command.parent.parent.name |
| 194 | + version = output.stdout.strip() or output.stderr.strip() |
| 195 | + print(f'{venv_name}: {version}') |
| 196 | + except FileNotFoundError: |
| 197 | + pass |
| 198 | +" |
| 199 | +} |
| 200 | + |
| 201 | +# Upgrade pip in all virtualenvs (from your virtualenv.justfile) |
| 202 | +upgrade_all_virtualenvs() { |
| 203 | + for venv_dir in "${HOME}/.virtualenvs"/*/; do |
| 204 | + if [[ -f "${venv_dir}bin/python" ]]; then |
| 205 | + echo "Upgrading: $(basename "$venv_dir")" |
| 206 | + "${venv_dir}bin/python" --version |
| 207 | + "${venv_dir}bin/python" -m pip --version |
| 208 | + "${venv_dir}bin/python" -m pip install --upgrade pip uv |
| 209 | + echo |
| 210 | + fi |
| 211 | + done |
| 212 | +} |
| 213 | + |
| 214 | +# Enhanced use_python function with virtualenv management |
1 | 215 | use_python() { |
2 | | - local python_root=$HOME/.pyenv/versions/$1 |
| 216 | + local python_version="$1" |
| 217 | + local python_root="$HOME/.pyenv/versions/$python_version" |
| 218 | + |
| 219 | + if [[ ! -d "$python_root" ]]; then |
| 220 | + log_error "Python version $python_version not found in pyenv" |
| 221 | + return 1 |
| 222 | + fi |
| 223 | + |
3 | 224 | load_prefix "$python_root" |
4 | 225 | layout_python "$python_root/bin/python" |
5 | 226 | } |
| 227 | + |
| 228 | +# Use a specific virtualenv by name (replaces workon) |
| 229 | +use_virtualenv() { |
| 230 | + local venv_name="$1" |
| 231 | + local venv_dir="${HOME}/.virtualenvs/${venv_name}" |
| 232 | + |
| 233 | + if [[ ! -d "$venv_dir" ]]; then |
| 234 | + log_error "Virtual environment '$venv_name' not found" |
| 235 | + return 1 |
| 236 | + fi |
| 237 | + |
| 238 | + export VIRTUAL_ENV="$venv_dir" |
| 239 | + export VIRTUAL_ENV_NAME="$venv_name" |
| 240 | + PATH_add "$venv_dir/bin" |
| 241 | + |
| 242 | + # Rich output for activation |
| 243 | + if command -v uvx >/dev/null 2>&1; then |
| 244 | + uvx --quiet rich --print "[green]Virtual environment activated[/green]: $venv_name" |
| 245 | + fi |
| 246 | +} |
| 247 | + |
| 248 | +# Hook into direnv's directory change detection |
| 249 | +# This replaces the need for manual workon/deactivate |
| 250 | +if [[ -n "$DIRENV_DIR" ]]; then |
| 251 | + auto_virtualenv |
| 252 | +fi |
0 commit comments