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 <