diff --git a/README.rst b/README.rst index cbdf8fa..40848f1 100644 --- a/README.rst +++ b/README.rst @@ -324,6 +324,67 @@ By default `mkvenv` will install setup.py via pip in `editable (i.e. development `__. To change this set ``AUTOSWITCH_PIPINSTALL`` to ``FULL``. +**Set pre and post hooks to run before and after virtual environment activation** + +Autoswitch Virtualenv allows you to define custom actions to be executed +before and after a virtual environment is activated. This feature is useful +for running setup scripts, updating environment variables, or performing any +other tasks that should occur around the activation process. + +Usage: + +To set pre and post hooks, use the `autoswitch_add_pre_hook` and +`autoswitch_add_post_hook` functions. These functions accept a string containing +the commands you want to execute. + +:: + + autoswitch_add_pre_hook ' + echo "Starting pre-hook" + for i in {1..3}; do + echo "Pre-hook step $i" + done + ' + + autoswitch_add_post_hook ' + echo "Starting post-hook" + for i in {1..3}; do + echo "Post-hook step $i" + done + ' + +In the example above: +- The pre-hook will run before the virtual environment is activated. +- The post-hook will run after the virtual environment has been successfully activated. + +You can add multiple pre and post hooks. They will be executed in the order they were added. + +Additional Information: + +1. Hooks are executed in a subshell, so they cannot modify the parent shell's environment directly. +2. If you need to modify environment variables or perform actions that affect the current shell, + consider using the `eval` command within your hook. +3. Hooks should be added after the Autoswitch Virtualenv plugin is loaded in your Zsh configuration. +4. Pre-hooks run regardless of whether the activation is successful, while post-hooks only run if + the activation succeeds. +5. Be cautious with the commands you include in hooks, especially if they modify the system or + project state. + +Example of a more practical post-hook: + +:: + + autoswitch_add_post_hook ' + if [ -n "$VIRTUAL_ENV" ]; then + echo "Activated virtualenv: $VIRTUAL_ENV" + pip list + fi + ' + +This post-hook will display the path of the activated virtual environment and list all installed packages whenever a virtualenv is activated. + +Remember to test your hooks thoroughly to ensure they don't introduce unexpected behavior in your development workflow. + Security Warnings ----------------- diff --git a/autoswitch_virtualenv.plugin.zsh b/autoswitch_virtualenv.plugin.zsh index cec9d9f..be88c9c 100644 --- a/autoswitch_virtualenv.plugin.zsh +++ b/autoswitch_virtualenv.plugin.zsh @@ -1,6 +1,13 @@ export AUTOSWITCH_VERSION="3.7.1" export AUTOSWITCH_FILE=".venv" +# Arrays to store hook functions +typeset -a AUTOSWITCH_PRE_HOOKS_ON_ACTIVATE +typeset -a AUTOSWITCH_POST_HOOKS_ON_ACTIVATE +typeset -a AUTOSWITCH_PRE_HOOKS_ON_DEACTIVATE +typeset -a AUTOSWITCH_POST_HOOKS_ON_DEACTIVATE + + AUTOSWITCH_RED="\e[31m" AUTOSWITCH_GREEN="\e[32m" AUTOSWITCH_PURPLE="\e[35m" @@ -9,6 +16,66 @@ AUTOSWITCH_NORMAL="\e[0m" VIRTUAL_ENV_DIR="${AUTOSWITCH_VIRTUAL_ENV_DIR:-$HOME/.virtualenvs}" +# Function to add pre-hook for activation +autoswitch_add_pre_hook_on_activate() { + local hook_name="AUTOSWITCH_PRE_HOOK_ON_ACTIVATE_${#AUTOSWITCH_PRE_HOOKS_ON_ACTIVATE[@]}" + eval "$hook_name() { $@ }" + AUTOSWITCH_PRE_HOOKS_ON_ACTIVATE+=("$hook_name") +} + +# Function to add post-hook for activation +autoswitch_add_post_hook_on_activate() { + local hook_name="AUTOSWITCH_POST_HOOK_ON_ACTIVATE_${#AUTOSWITCH_POST_HOOKS_ON_ACTIVATE[@]}" + eval "$hook_name() { $@ }" + AUTOSWITCH_POST_HOOKS_ON_ACTIVATE+=("$hook_name") +} + +# Function to add pre-hook for deactivation +autoswitch_add_pre_hook_on_deactivate() { + local hook_name="AUTOSWITCH_PRE_HOOK_ON_DEACTIVATE_${#AUTOSWITCH_PRE_HOOKS_ON_DEACTIVATE[@]}" + eval "$hook_name() { $@ }" + AUTOSWITCH_PRE_HOOKS_ON_DEACTIVATE+=("$hook_name") +} + +# Function to add post-hook for deactivation +autoswitch_add_post_hook_on_deactivate() { + local hook_name="AUTOSWITCH_POST_HOOK_ON_DEACTIVATE_${#AUTOSWITCH_POST_HOOKS_ON_DEACTIVATE[@]}" + eval "$hook_name() { $@ }" + AUTOSWITCH_POST_HOOKS_ON_DEACTIVATE+=("$hook_name") +} + +# Function to execute pre-hooks on activation +_execute_pre_hooks_on_activate() { + local hook + for hook in "${AUTOSWITCH_PRE_HOOKS_ON_ACTIVATE[@]}"; do + $hook + done +} + +# Function to execute post-hooks on activation +_execute_post_hooks_on_activate() { + local hook + for hook in "${AUTOSWITCH_POST_HOOKS_ON_ACTIVATE[@]}"; do + $hook + done +} + +# Function to execute pre-hooks on deactivation +_execute_pre_hooks_on_deactivate() { + local hook + for hook in "${AUTOSWITCH_PRE_HOOKS_ON_DEACTIVATE[@]}"; do + $hook + done +} + +# Function to execute post-hooks on deactivation +_execute_post_hooks_on_deactivate() { + local hook + for hook in "${AUTOSWITCH_POST_HOOKS_ON_DEACTIVATE[@]}"; do + $hook + done +} + function _validated_source() { local target_path="$1" @@ -190,22 +257,36 @@ function check_venv() printf "Run the following command to fix this: ${AUTOSWITCH_PURPLE}\"chmod 600 $venv_path\"${AUTOSWITCH_NORMAL}\n" else if [[ "$venv_path" == *"/Pipfile" ]]; then - if type "pipenv" > /dev/null && _activate_pipenv; then - return + if type "pipenv" > /dev/null; then + _execute_pre_hooks_on_activate + if _activate_pipenv; then + _execute_post_hooks_on_activate + return + fi fi elif [[ "$venv_path" == *"/poetry.lock" ]]; then - if type "poetry" > /dev/null && _activate_poetry; then - return + if type "poetry" > /dev/null; then + _execute_pre_hooks_on_activate + if _activate_poetry; then + _execute_post_hooks_on_activate + return + fi fi # standard use case: $venv_path is a file containing a virtualenv name elif [[ -f "$venv_path" ]]; then local switch_to="$(<"$venv_path")" - _maybeworkon "$(_virtual_env_dir "$switch_to")" "virtualenv" - return + _execute_pre_hooks_on_activate + if _maybeworkon "$(_virtual_env_dir "$switch_to")" "virtualenv"; then + _execute_post_hooks_on_activate + return + fi # $venv_path actually is itself a virtualenv elif [[ -d "$venv_path" ]] && [[ -f "$venv_path/bin/activate" ]]; then - _maybeworkon "$venv_path" "virtualenv" - return + _execute_pre_hooks_on_activate + if _maybeworkon "$venv_path" "virtualenv"; then + _execute_post_hooks_on_activate + return + fi fi fi fi @@ -217,6 +298,7 @@ function check_venv() printf "Python ${AUTOSWITCH_PURPLE}$venv_type${AUTOSWITCH_NORMAL} project detected. " printf "Run ${AUTOSWITCH_PURPLE}mkvenv${AUTOSWITCH_NORMAL} to setup autoswitching\n" fi + _default_venv } @@ -230,7 +312,11 @@ function _default_venv() elif [[ -n "$VIRTUAL_ENV" ]]; then local venv_name="$(_get_venv_name "$VIRTUAL_ENV" "$venv_type")" _autoswitch_message "Deactivating: ${AUTOSWITCH_BOLD}${AUTOSWITCH_PURPLE}%s${AUTOSWITCH_NORMAL}\n" "$venv_name" + + # Execute deactivation hooks + _execute_pre_hooks_on_deactivate deactivate + _execute_post_hooks_on_deactivate fi }