Fix POSIX sh (dash) compatibility in pyenv-virtualenv-init (#526)

* Refactor cache code generation
* Don't generate prompt_command for non bash/zsh/fish

---------

Co-authored-by: Ivan Pozdeev <vano@mail.mipt.ru>
This commit is contained in:
Chris Caldwell 2026-04-21 09:53:01 -06:00 committed by GitHub
parent 6a832cacbb
commit 99d35f201c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 62 deletions

View file

@ -120,22 +120,13 @@ esac
case "$shell" in case "$shell" in
fish ) fish )
if [ -n "$_has_version_hooks" ]; then cat <<EOS
cat <<EOS
function _pyenv_virtualenv_hook --on-event fish_prompt; function _pyenv_virtualenv_hook --on-event fish_prompt;
set -l ret \$status set -l ret \$status
if [ -n "\$VIRTUAL_ENV" ]
pyenv activate --quiet; or pyenv deactivate --quiet; or true
else
pyenv activate --quiet; or true
end
return \$ret
end
EOS EOS
else
if [ -z "$_has_version_hooks" ]; then
cat <<EOS cat <<EOS
function _pyenv_virtualenv_hook --on-event fish_prompt;
set -l ret \$status
if test "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\ if test "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\
-a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" -a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV"
if test -n "\$PYENV_VERSION" if test -n "\$PYENV_VERSION"
@ -146,11 +137,19 @@ function _pyenv_virtualenv_hook --on-event fish_prompt;
return \$ret return \$ret
end end
end end
EOS
fi
cat <<EOS
if [ -n "\$VIRTUAL_ENV" ] if [ -n "\$VIRTUAL_ENV" ]
pyenv activate --quiet; or pyenv deactivate --quiet; or true pyenv activate --quiet; or pyenv deactivate --quiet; or true
else else
pyenv activate --quiet; or true pyenv activate --quiet; or true
end end
EOS
if [ -z "$_has_version_hooks" ]; then
cat <<EOS
set -g _PYENV_VH_PWD "\$PWD" set -g _PYENV_VH_PWD "\$PWD"
set -g _PYENV_VH_VERSION "\$PYENV_VERSION" set -g _PYENV_VH_VERSION "\$PYENV_VERSION"
set -g _PYENV_VH_VENV "\$VIRTUAL_ENV" set -g _PYENV_VH_VENV "\$VIRTUAL_ENV"
@ -175,39 +174,23 @@ function _pyenv_virtualenv_hook --on-event fish_prompt;
set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$PYENV_ROOT/version" set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$PYENV_ROOT/version"
end end
set -g _PYENV_VH_MTIMES (stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null) set -g _PYENV_VH_MTIMES (stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null)
EOS
fi
cat <<EOS
return \$ret return \$ret
end end
EOS
fi
;;
ksh )
cat <<EOS
function _pyenv_virtualenv_hook() {
EOS EOS
;; ;;
* )
bash|zsh )
cat <<EOS cat <<EOS
_pyenv_virtualenv_hook() { _pyenv_virtualenv_hook() {
local ret=\$?
EOS EOS
;;
esac
if [[ "$shell" != "fish" ]]; then if [ -z "$_has_version_hooks" ]; then
if [ -n "$_has_version_hooks" ]; then
cat <<EOS cat <<EOS
local ret=\$?
if [ -n "\${VIRTUAL_ENV-}" ]; then
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
else
eval "\$(pyenv sh-activate --quiet || true)" || true
fi
return \$ret
};
EOS
else
cat <<EOS
local ret=\$?
# Cache: env vars checked once, path list and stat rebuilt on miss only
if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
if [ -n "\${PYENV_VERSION-}" ]; then if [ -n "\${PYENV_VERSION-}" ]; then
@ -218,11 +201,19 @@ EOS
return \$ret return \$ret
fi fi
fi fi
EOS
fi
cat <<EOS
if [ -n "\${VIRTUAL_ENV-}" ]; then if [ -n "\${VIRTUAL_ENV-}" ]; then
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
else else
eval "\$(pyenv sh-activate --quiet || true)" || true eval "\$(pyenv sh-activate --quiet || true)" || true
fi fi
EOS
if [ -z "$_has_version_hooks" ]; then
cat <<EOS
_PYENV_VH_PWD="\${PWD}" _PYENV_VH_PWD="\${PWD}"
_PYENV_VH_VERSION="\${PYENV_VERSION-}" _PYENV_VH_VERSION="\${PYENV_VERSION-}"
_PYENV_VH_VENV="\${VIRTUAL_ENV-}" _PYENV_VH_VENV="\${VIRTUAL_ENV-}"
@ -246,29 +237,33 @@ EOS
_PYENV_VH_PATHS+=("\${PYENV_ROOT}/version") _PYENV_VH_PATHS+=("\${PYENV_ROOT}/version")
fi fi
_PYENV_VH_MTIMES="\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)" _PYENV_VH_MTIMES="\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)"
return \$ret
};
EOS EOS
fi fi
case "$shell" in cat <<EOS
bash ) return \$ret
cat <<EOS };
EOS
;;
esac
case "$shell" in
bash )
cat <<EOS
if ! [[ "\${PROMPT_COMMAND-}" =~ _pyenv_virtualenv_hook ]]; then if ! [[ "\${PROMPT_COMMAND-}" =~ _pyenv_virtualenv_hook ]]; then
PROMPT_COMMAND="_pyenv_virtualenv_hook;\${PROMPT_COMMAND-}" PROMPT_COMMAND="_pyenv_virtualenv_hook;\${PROMPT_COMMAND-}"
fi fi
EOS EOS
;; ;;
zsh ) zsh )
cat <<EOS cat <<EOS
typeset -g -a precmd_functions typeset -g -a precmd_functions
if [[ -z \$precmd_functions[(r)_pyenv_virtualenv_hook] ]]; then if [[ -z \$precmd_functions[(r)_pyenv_virtualenv_hook] ]]; then
precmd_functions=(_pyenv_virtualenv_hook \$precmd_functions); precmd_functions=(_pyenv_virtualenv_hook \$precmd_functions);
fi fi
EOS EOS
;; ;;
* ) * )
# FIXME: what should i do here?? # No prompt command support or it's installed elsewhere
;; ;;
esac esac
fi

View file

@ -29,22 +29,18 @@ load test_helper
rm -f "${TMP}/script.sh" rm -f "${TMP}/script.sh"
} }
@test "sh-compatible instructions" {
run pyenv-virtualenv-init bash
assert [ "$status" -eq 1 ]
assert_output_contains 'eval "$(pyenv virtualenv-init -)"'
run pyenv-virtualenv-init zsh
assert [ "$status" -eq 1 ]
assert_output_contains 'eval "$(pyenv virtualenv-init -)"'
}
@test "fish instructions" { @test "fish instructions" {
run pyenv-virtualenv-init fish run pyenv-virtualenv-init fish
assert [ "$status" -eq 1 ] assert [ "$status" -eq 1 ]
assert_output_contains 'status --is-interactive; and source (pyenv virtualenv-init -|psub)' assert_output_contains 'status --is-interactive; and source (pyenv virtualenv-init -|psub)'
} }
@test "other shells instructions" {
run pyenv-virtualenv-init some_other_sh
assert [ "$status" -eq 1 ]
assert_output_contains 'eval "$(pyenv virtualenv-init -)"'
}
@test "outputs bash-specific syntax" { @test "outputs bash-specific syntax" {
export PYENV_VIRTUALENV_ROOT="${TMP}/pyenv/plugins/pyenv-virtualenv" export PYENV_VIRTUALENV_ROOT="${TMP}/pyenv/plugins/pyenv-virtualenv"
run pyenv-virtualenv-init - bash run pyenv-virtualenv-init - bash
@ -54,7 +50,6 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}";
export PYENV_VIRTUALENV_INIT=1; export PYENV_VIRTUALENV_INIT=1;
_pyenv_virtualenv_hook() { _pyenv_virtualenv_hook() {
local ret=\$? local ret=\$?
# Cache: env vars checked once, path list and stat rebuilt on miss only
if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
if [ -n "\${PYENV_VERSION-}" ]; then if [ -n "\${PYENV_VERSION-}" ]; then
@ -165,7 +160,6 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}";
export PYENV_VIRTUALENV_INIT=1; export PYENV_VIRTUALENV_INIT=1;
_pyenv_virtualenv_hook() { _pyenv_virtualenv_hook() {
local ret=\$? local ret=\$?
# Cache: env vars checked once, path list and stat rebuilt on miss only
if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
if [ -n "\${PYENV_VERSION-}" ]; then if [ -n "\${PYENV_VERSION-}" ]; then
@ -212,3 +206,13 @@ if [[ -z \$precmd_functions[(r)_pyenv_virtualenv_hook] ]]; then
fi fi
EOS EOS
} }
@test "outputs other shells syntax" {
export PYENV_VIRTUALENV_ROOT="${TMP}/pyenv/plugins/pyenv-virtualenv"
run pyenv-virtualenv-init - some_other_sh
assert_success
assert_output <<EOS
export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}";
export PYENV_VIRTUALENV_INIT=1;
EOS
}