diff --git a/.gitignore b/.gitignore index a63d45e3..d83f49c9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ /libexec/*.dylib /src/Makefile /src/*.o -bats/ +/bats/ diff --git a/README.md b/README.md index 10f36ab1..415a2306 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,31 @@ To upgrade to a specific release of pyenv, check out the corresponding tag: v0.1.0 $ git checkout v0.1.0 +### Uninstalling pyenv + +The simplicity of pyenv makes it easy to temporarily disable it, or +uninstall from the system. + +1. To **disable** pyenv managing your Python versions, simply remove the + `pyenv init` line from your shell startup configuration. This will + remove pyenv shims directory from PATH, and future invocations like + `python` will execute the system Python version, as before pyenv. + + `pyenv` will still be accessible on the command line, but your Python + apps won't be affected by version switching. + +2. To completely **uninstall** pyenv, perform step (1) and then remove + its root directory. This will **delete all Python versions** that were + installed under `` `pyenv root`/versions/ `` directory: + + rm -rf `pyenv root` + + If you've installed pyenv using a package manager, as a final step + perform the pyenv package removal. For instance, for Homebrew: + + brew uninstall pyenv + +## Command Reference ### Homebrew on Mac OS X @@ -328,43 +353,32 @@ See [COMMANDS.md](COMMANDS.md). ---- +## Environment variables + +You can affect how pyenv operates with the following settings: + +name | default | description +-----|---------|------------ +`PYENV_VERSION` | | Specifies the Python version to be used.
Also see [`pyenv shell`](#pyenv-shell) +`PYENV_ROOT` | `~/.pyenv` | Defines the directory under which Python versions and shims reside.
Also see `pyenv root` +`PYENV_DEBUG` | | Outputs debug information.
Also as: `pyenv --debug ` +`PYENV_HOOK_PATH` | [_see wiki_][hooks] | Colon-separated list of paths searched for pyenv hooks. +`PYENV_DIR` | `$PWD` | Directory to start searching for `.python-version` files. ## Development -The pyenv source code is [hosted on GitHub](https://github.com/yyuu/pyenv). -It's clean, modular, and easy to understand--even if you're not a shell hacker. +The pyenv source code is [hosted on +GitHub](https://github.com/yyuu/pyenv). It's clean, modular, +and easy to understand, even if you're not a shell hacker. -Please feel free to submit Pull Requests and report bugs on the -[issue tracker](https://github.com/yyuu/pyenv/issues). +Tests are executed using [Bats](https://github.com/sstephenson/bats): + + $ bats test + $ bats/test/.bats + +Please feel free to submit pull requests and file bugs on the [issue +tracker](https://github.com/yyuu/pyenv/issues). -### Version History - -See [CHANGELOG.md](CHANGELOG.md). - - -### License - -(The MIT license) - -* Copyright (c) 2013 Yamashita, Yuu -* Copyright (c) 2013 Sam Stephenson - -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. + [pyenv-virtualenv]: https://github.com/yyuu/pyenv-virtualenv#readme + [hooks]: https://github.com/yyuu/pyenv/wiki/Authoring-plugins#pyenv-hooks diff --git a/libexec/pyenv b/libexec/pyenv index 8173f6dc..ff0ee417 100755 --- a/libexec/pyenv +++ b/libexec/pyenv @@ -12,21 +12,24 @@ if [ -n "$PYENV_DEBUG" ]; then set -x fi -if enable -f "${0%/*}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then +abort() { + { if [ "$#" -eq 0 ]; then cat - + else echo "pyenv: $*" + fi + } >&2 + exit 1 +} + +if enable -f "${BASH_SOURCE%/*}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then abs_dirname() { local path="$(realpath "$1")" echo "${path%/*}" } else - if [ -n "$PYENV_NATIVE_EXT" ]; then - echo "pyenv: failed to load \`realpath' builtin" >&2 - exit 1 - fi + [ -z "$PYENV_NATIVE_EXT" ] || abort "failed to load \`realpath' builtin" + READLINK=$(type -p greadlink readlink | head -1) -if [ -z "$READLINK" ]; then - echo "pyenv: cannot find readlink - are you missing GNU coreutils?" >&2 - exit 1 -fi +[ -n "$READLINK" ] || abort "cannot find readlink - are you missing GNU coreutils?" resolve_link() { $READLINK "$1" @@ -70,10 +73,7 @@ fi if [ -z "${PYENV_DIR}" ]; then PYENV_DIR="$(pwd)" else - cd "$PYENV_DIR" 2>/dev/null || { - echo "pyenv: cannot change working directory to \`$PYENV_DIR'" - exit 1 - } >&2 + cd "$PYENV_DIR" 2>/dev/null || abort "cannot change working directory to \`$PYENV_DIR'" PYENV_DIR="$(pwd)" cd "$OLDPWD" fi @@ -104,20 +104,26 @@ shopt -u nullglob command="$1" case "$command" in -"" | "-h" | "--help" ) - echo -e "$(pyenv---version)\n$(pyenv-help)" >&2 +"" ) + { pyenv---version + pyenv-help + } | abort ;; -"-v" ) +-v | --version ) exec pyenv---version ;; +-h | --help ) + exec pyenv-help + ;; * ) command_path="$(command -v "pyenv-$command" || true)" - if [ -z "$command_path" ]; then - echo "pyenv: no such command \`$command'" >&2 - exit 1 - fi + [ -n "$command_path" ] || abort "no such command \`$command'" shift 1 - exec "$command_path" "$@" + if [ "$1" = --help ]; then + exec pyenv-help "$command" + else + exec "$command_path" "$@" + fi ;; esac diff --git a/libexec/pyenv---version b/libexec/pyenv---version index 49e262b6..8544560b 100755 --- a/libexec/pyenv---version +++ b/libexec/pyenv---version @@ -13,10 +13,14 @@ set -e [ -n "$PYENV_DEBUG" ] && set -x version="20151105" +git_revision="" -if cd "$PYENV_ROOT" 2>/dev/null; then - git_revision="$(git describe --tags HEAD 2>/dev/null || true)" - git_revision="${git_revision#v}" -fi +for source_dir in "${BASH_SOURCE%/*}" "$PYENV_ROOT"; do + if cd "$source_dir" 2>/dev/null && git remote -v 2>/dev/null | grep -q pyenv; then + git_revision="$(git describe --tags HEAD 2>/dev/null || true)" + git_revision="${git_revision#v}" + [ -z "$git_revision" ] || break + fi +done echo "pyenv ${git_revision:-$version}" diff --git a/libexec/pyenv-completions b/libexec/pyenv-completions index b75ad813..227b932e 100755 --- a/libexec/pyenv-completions +++ b/libexec/pyenv-completions @@ -10,7 +10,16 @@ if [ -z "$COMMAND" ]; then exit 1 fi +# Provide pyenv completions +if [ "$COMMAND" = "--complete" ]; then + exec pyenv-commands +fi + COMMAND_PATH="$(command -v "pyenv-$COMMAND" || command -v "pyenv-sh-$COMMAND")" + +# --help is provided automatically +echo --help + if grep -iE "^([#%]|--|//) provide pyenv completions" "$COMMAND_PATH" >/dev/null; then shift exec "$COMMAND_PATH" --complete "$@" diff --git a/libexec/pyenv-help b/libexec/pyenv-help index b5b49c06..9a04d9a6 100755 --- a/libexec/pyenv-help +++ b/libexec/pyenv-help @@ -15,6 +15,12 @@ set -e [ -n "$PYENV_DEBUG" ] && set -x +# Provide pyenv completions +if [ "$1" = "--complete" ]; then + echo --usage + exec pyenv-commands +fi + command_path() { local command="$1" command -v pyenv-"$command" || command -v pyenv-sh-"$command" || true diff --git a/libexec/pyenv-hooks b/libexec/pyenv-hooks index fc4b2c06..71c66bef 100755 --- a/libexec/pyenv-hooks +++ b/libexec/pyenv-hooks @@ -35,16 +35,17 @@ resolve_link() { } realpath() { - local cwd="$(pwd)" + local cwd="$PWD" local path="$1" + local name while [ -n "$path" ]; do - cd "${path%/*}" - local name="${path##*/}" + name="${path##*/}" + [ "$name" = "$path" ] || cd "${path%/*}" path="$(resolve_link "$name" || true)" done - echo "$(pwd)/$name" + echo "${PWD}/$name" cd "$cwd" } fi diff --git a/libexec/pyenv-init b/libexec/pyenv-init index 09847120..bf3924c3 100755 --- a/libexec/pyenv-init +++ b/libexec/pyenv-init @@ -5,6 +5,17 @@ set -e [ -n "$PYENV_DEBUG" ] && set -x +# Provide pyenv completions +if [ "$1" = "--complete" ]; then + echo - + echo --no-rehash + echo bash + echo fish + echo ksh + echo zsh + exit +fi + print="" no_rehash="" for args in "$@" @@ -25,7 +36,8 @@ if [ -z "$shell" ]; then shell="$(ps c -p "$PPID" -o 'ucomm=' 2>/dev/null || true)" shell="${shell##-}" shell="${shell%% *}" - shell="$(basename "${shell:-$SHELL}")" + shell="${shell:-$SHELL}" + shell="${shell##*/}" fi root="${0%/*}/.." @@ -33,7 +45,11 @@ root="${0%/*}/.." if [ -z "$print" ]; then case "$shell" in bash ) - profile='~/.bash_profile' + if [ -f "${HOME}/.bashrc" ] && [ ! -f "${HOME}/.bash_profile" ]; then + profile='~/.bashrc' + else + profile='~/.bash_profile' + fi ;; zsh ) profile='~/.zshrc' @@ -49,8 +65,8 @@ if [ -z "$print" ]; then ;; esac - { echo "# Load pyenv automatically by adding" - echo "# the following to the end of ${profile}:" + { echo "# Load pyenv automatically by appending" + echo "# the following to ${profile}:" echo case "$shell" in fish ) @@ -88,7 +104,7 @@ if [ -r "$completion" ]; then fi if [ -z "$no_rehash" ]; then - echo 'pyenv rehash 2>/dev/null' + echo 'command pyenv rehash 2>/dev/null' fi commands=(`pyenv-commands --sh`) @@ -101,7 +117,7 @@ function pyenv switch "\$command" case ${commands[*]} - eval (pyenv "sh-\$command" \$argv) + . (pyenv "sh-\$command" \$argv|psub) case '*' command pyenv "\$command" \$argv end @@ -132,7 +148,7 @@ cat <&2 + echo "pyenv: version \`$version' is not installed (set by $(pyenv-version-origin))" >&2 any_not_installed=1 fi done diff --git a/libexec/pyenv-versions b/libexec/pyenv-versions index 45c16d39..5944da00 100755 --- a/libexec/pyenv-versions +++ b/libexec/pyenv-versions @@ -1,13 +1,69 @@ #!/usr/bin/env bash # Summary: List all Python versions available to pyenv -# Usage: pyenv versions [--bare] +# Usage: pyenv versions [--bare] [--skip-aliases] # # Lists all Python versions found in `$PYENV_ROOT/versions/*'. set -e [ -n "$PYENV_DEBUG" ] && set -x -if [ "$1" = "--bare" ]; then +unset bare +unset skip_aliases +# Provide pyenv completions +for arg; do + case "$arg" in + --complete ) + echo --bare + echo --skip-aliases + exit ;; + --bare ) bare=1 ;; + --skip-aliases ) skip_aliases=1 ;; + * ) + pyenv-help --usage versions >&2 + exit 1 + ;; + esac +done + +versions_dir="${PYENV_ROOT}/versions" + +if ! enable -f "${BASH_SOURCE%/*}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then + if [ -n "$PYENV_NATIVE_EXT" ]; then + echo "pyenv: failed to load \`realpath' builtin" >&2 + exit 1 + fi + + READLINK=$(type -p greadlink readlink | head -1) + if [ -z "$READLINK" ]; then + echo "pyenv: cannot find readlink - are you missing GNU coreutils?" >&2 + exit 1 + fi + + resolve_link() { + $READLINK "$1" + } + + realpath() { + local cwd="$PWD" + local path="$1" + local name + + while [ -n "$path" ]; do + name="${path##*/}" + [ "$name" = "$path" ] || cd "${path%/*}" + path="$(resolve_link "$name" || true)" + done + + echo "${PWD}/$name" + cd "$cwd" + } +fi + +if [ -d "$versions_dir" ]; then + versions_dir="$(realpath "$versions_dir")" +fi + +if [ -n "$bare" ]; then hit_prefix="" miss_prefix="" current_versions=() @@ -50,8 +106,12 @@ if [ -n "$include_system" ] && PYENV_VERSION=system pyenv-which python >/dev/nul fi shopt -s nullglob -for path in "${PYENV_ROOT}/versions/"*; do +for path in "$versions_dir"/*; do if [ -d "$path" ]; then + if [ -n "$skip_aliases" ] && [ -L "$path" ]; then + target="$(realpath "$path")" + [ "${target%/*}" != "$versions_dir" ] || continue + fi print_version "${path##*/}" # virtual environments created by anaconda/miniconda for env_path in "${path}/envs/"*; do diff --git a/libexec/pyenv-which b/libexec/pyenv-which index 675c70fe..42d8d298 100755 --- a/libexec/pyenv-which +++ b/libexec/pyenv-which @@ -58,6 +58,9 @@ done if [ -x "$PYENV_COMMAND_PATH" ]; then echo "$PYENV_COMMAND_PATH" +elif [ "$PYENV_VERSION" != "system" ] && [ ! -d "${PYENV_ROOT}/versions/${PYENV_VERSION}" ]; then + echo "pyenv: version \`$PYENV_VERSION' is not installed (set by $(pyenv-version-origin))" >&2 + exit 1 else any_not_installed=0 for version in "${versions[@]}"; do diff --git a/test/--version.bats b/test/--version.bats index 77196929..c5410a74 100644 --- a/test/--version.bats +++ b/test/--version.bats @@ -6,6 +6,18 @@ setup() { mkdir -p "$HOME" git config --global user.name "Tester" git config --global user.email "tester@test.local" + + mkdir -p "${PYENV_TEST_DIR}/bin" + cat > "${PYENV_TEST_DIR}/bin/git" <&2 + exit 1 +else + exec $(which git) "\$@" +fi +CMD + chmod +x "${PYENV_TEST_DIR}/bin/git" } git_commit() { @@ -19,10 +31,25 @@ git_commit() { [[ $output == "pyenv 20"* ]] } +@test "doesn't read version from non-pyenv repo" { + mkdir -p "$PYENV_ROOT" + cd "$PYENV_ROOT" + git init + git remote add origin https://github.com/homebrew/homebrew.git + git_commit + git tag v1.0 + + cd "$PYENV_TEST_DIR" + run pyenv---version + assert_success + [[ $output == "pyenv 20"* ]] +} + @test "reads version from git repo" { mkdir -p "$PYENV_ROOT" cd "$PYENV_ROOT" git init + git remote add origin https://github.com/yyuu/pyenv.git git_commit git tag v20380119 git_commit @@ -38,6 +65,7 @@ git_commit() { mkdir -p "$PYENV_ROOT" cd "$PYENV_ROOT" git init + git remote add origin https://github.com/yyuu/pyenv.git git_commit cd "$PYENV_TEST_DIR" diff --git a/test/completions.bats b/test/completions.bats index f18a088c..e83e91e5 100644 --- a/test/completions.bats +++ b/test/completions.bats @@ -13,7 +13,7 @@ create_command() { create_command "pyenv-hello" "#!$BASH echo hello" run pyenv-completions hello - assert_success "" + assert_success "--help" } @test "command with completion support" { @@ -25,7 +25,11 @@ else exit 1 fi" run pyenv-completions hello - assert_success "hello" + assert_success + assert_output < .python-version + run pyenv-exec rspec + assert_failure "pyenv: version \`2.7' is not installed (set by $PWD/.python-version)" } @test "completes with names of executables" { @@ -29,6 +37,7 @@ create_executable() { run pyenv-completions exec assert_success assert_output </dev/null" + assert_line "command pyenv rehash 2>/dev/null" } @test "setup shell completions" { diff --git a/test/pyenv.bats b/test/pyenv.bats index 3d9fb593..4fced1f1 100644 --- a/test/pyenv.bats +++ b/test/pyenv.bats @@ -4,8 +4,8 @@ load test_helper @test "blank invocation" { run pyenv - assert_success - assert [ "${lines[0]}" == "pyenv 20151105" ] + assert_failure + assert_line 0 "$(pyenv---version)" } @test "invalid command" { diff --git a/test/test_helper.bash b/test/test_helper.bash index 6e5015c6..8db1a001 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -1,18 +1,20 @@ unset PYENV_VERSION unset PYENV_DIR -if enable -f "${BATS_TEST_DIRNAME}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then - PYENV_TEST_DIR="$(realpath "$BATS_TMPDIR")/pyenv" -else - if [ -n "$PYENV_NATIVE_EXT" ]; then - echo "pyenv: failed to load \`realpath' builtin" >&2 - exit 1 - fi - PYENV_TEST_DIR="${BATS_TMPDIR}/pyenv" -fi - # guard against executing this block twice due to bats internals -if [ "$PYENV_ROOT" != "${PYENV_TEST_DIR}/root" ]; then +if [ -z "$PYENV_TEST_DIR" ]; then + PYENV_TEST_DIR="${BATS_TMPDIR}/pyenv" + export PYENV_TEST_DIR="$(mktemp -d "${PYENV_TEST_DIR}.XXX" 2>/dev/null || echo "$PYENV_TEST_DIR")" + + if enable -f "${BATS_TEST_DIRNAME}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then + export PYENV_TEST_DIR="$(realpath "$PYENV_TEST_DIR")" + else + if [ -n "$PYENV_NATIVE_EXT" ]; then + echo "pyenv: failed to load \`realpath' builtin" >&2 + exit 1 + fi + fi + export PYENV_ROOT="${PYENV_TEST_DIR}/root" export HOME="${PYENV_TEST_DIR}/home" @@ -22,6 +24,9 @@ if [ "$PYENV_ROOT" != "${PYENV_TEST_DIR}/root" ]; then PATH="${BATS_TEST_DIRNAME}/libexec:$PATH" PATH="${PYENV_ROOT}/shims:$PATH" export PATH + + for xdg_var in `env 2>/dev/null | grep ^XDG_ | cut -d= -f1`; do unset "$xdg_var"; done + unset xdg_var fi teardown() { @@ -106,7 +111,7 @@ assert() { # but in which system utils necessary for pyenv operation are still available. path_without() { local exe="$1" - local path="${PATH}:" + local path=":${PATH}:" local found alt util for found in $(which -a "$exe"); do found="${found%/*}" @@ -118,8 +123,9 @@ path_without() { ln -s "${found}/$util" "${alt}/$util" fi done - path="${path/${found}:/${alt}:}" + path="${path/:${found}:/:${alt}:}" fi done + path="${path#:}" echo "${path%:}" } diff --git a/test/version-name.bats b/test/version-name.bats index 6d386076..b968bb75 100644 --- a/test/version-name.bats +++ b/test/version-name.bats @@ -49,7 +49,7 @@ setup() { @test "missing version" { PYENV_VERSION=1.2 run pyenv-version-name - assert_failure "pyenv: version \`1.2' is not installed" + assert_failure "pyenv: version \`1.2' is not installed (set by PYENV_VERSION environment variable)" } @test "one missing version (second missing)" { @@ -57,7 +57,7 @@ setup() { PYENV_VERSION="3.4.2:1.2" run pyenv-version-name assert_failure assert_output <