diff --git a/.agignore b/.agignore new file mode 100644 index 00000000..3a0f7841 --- /dev/null +++ b/.agignore @@ -0,0 +1,2 @@ +./versions +./cache diff --git a/.vimrc b/.vimrc new file mode 100644 index 00000000..e12e62f0 --- /dev/null +++ b/.vimrc @@ -0,0 +1 @@ +set wildignore+=versions/*,cache/* diff --git a/CONDUCT.md b/CONDUCT.md new file mode 100644 index 00000000..aebb7899 --- /dev/null +++ b/CONDUCT.md @@ -0,0 +1,80 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting one of the project maintainers listed below. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Project Maintainers + +* Sam Stephenson <> +* Mislav Marohnić <> +* Erik Michaels-Ober <> + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/bin/python-local-exec b/bin/python-local-exec deleted file mode 100755 index 62e59328..00000000 --- a/bin/python-local-exec +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# -# `python-local-exec` is a drop-in replacement for the standard Python -# shebang line: -# -# #!/usr/bin/env python-local-exec -# -# Use it for scripts inside a project with an `.pyenv-version` -# file. When you run the scripts, they'll use the project-specified -# Python version, regardless of what directory they're run from. Useful -# for e.g. running project tasks in cron scripts without needing to -# `cd` into the project first. - -set -e -export PYENV_DIR="${1%/*}" - -[ -n "$PYENV_SILENCE_WARNINGS" ] || { - echo "pyenv: \`python-local-exec' is deprecated and will be removed in the next release." - echo " To upgrade: https://github.com/yyuu/pyenv/wiki/python-local-exec" - echo -} >&2 - -exec python "$@" diff --git a/libexec/pyenv b/libexec/pyenv index 5677a52c..24d5210f 100755 --- a/libexec/pyenv +++ b/libexec/pyenv @@ -97,6 +97,7 @@ PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib for plugin_hook in "${PYENV_ROOT}/plugins/"*/etc/pyenv.d; do PYENV_HOOK_PATH="${PYENV_HOOK_PATH}:${plugin_hook}" done +PYENV_HOOK_PATH="${PYENV_HOOK_PATH#:}" export PYENV_HOOK_PATH shopt -u nullglob diff --git a/libexec/pyenv---version b/libexec/pyenv---version index 45d718eb..8947ccf4 100755 --- a/libexec/pyenv---version +++ b/libexec/pyenv---version @@ -15,12 +15,9 @@ set -e version="20160202" git_revision="" -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 +if cd "${BASH_SOURCE%/*}" 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}" +fi echo "pyenv ${git_revision:-$version}" diff --git a/libexec/pyenv-hooks b/libexec/pyenv-hooks index 71c66bef..a861c9f1 100755 --- a/libexec/pyenv-hooks +++ b/libexec/pyenv-hooks @@ -9,6 +9,8 @@ set -e if [ "$1" = "--complete" ]; then echo exec echo rehash + echo version-name + echo version-origin echo which exit fi diff --git a/libexec/pyenv-init b/libexec/pyenv-init index bf3924c3..b95c32da 100755 --- a/libexec/pyenv-init +++ b/libexec/pyenv-init @@ -33,9 +33,9 @@ done shell="$1" if [ -z "$shell" ]; then - shell="$(ps c -p "$PPID" -o 'ucomm=' 2>/dev/null || true)" - shell="${shell##-}" + shell="$(ps -p "$PPID" -o 'args=' 2>/dev/null || true)" shell="${shell%% *}" + shell="${shell##-}" shell="${shell:-$SHELL}" shell="${shell##*/}" fi diff --git a/libexec/pyenv-local b/libexec/pyenv-local index b6536050..7efbf2a8 100755 --- a/libexec/pyenv-local +++ b/libexec/pyenv-local @@ -15,10 +15,6 @@ # `PYENV_VERSION' environment variable takes precedence over local # and global versions. # -# For backwards compatibility, pyenv will also read version -# specifications from `.pyenv-version' files, but a `.python-version' -# file in the same directory takes precedence. -# # should be a string matching a Python version known to pyenv. # The special version string `system' will use your default system Python. # Run `pyenv versions' for a list of available Python versions. diff --git a/libexec/pyenv-version-file b/libexec/pyenv-version-file index 374aaab0..eb2f5c62 100755 --- a/libexec/pyenv-version-file +++ b/libexec/pyenv-version-file @@ -1,35 +1,28 @@ #!/usr/bin/env bash +# Usage: pyenv version-file [] # Summary: Detect the file that sets the current pyenv version set -e [ -n "$PYENV_DEBUG" ] && set -x +target_dir="$1" + find_local_version_file() { local root="$1" - while true; do - [[ "$root" =~ ^//[^/]*$ ]] && break + while ! [[ "$root" =~ ^//[^/]*$ ]]; do if [ -e "${root}/.python-version" ]; then echo "${root}/.python-version" - exit - elif [ -e "${root}/.pyenv-version" ]; then - echo "${root}/.pyenv-version" - exit + return 0 fi [ -n "$root" ] || break root="${root%/*}" done + return 1 } -find_local_version_file "$PYENV_DIR" -[ "$PYENV_DIR" = "$PWD" ] || find_local_version_file "$PWD" - -global_version_file="${PYENV_ROOT}/version" - -if [ -e "$global_version_file" ]; then - echo "$global_version_file" -elif [ -e "${PYENV_ROOT}/global" ]; then - echo "${PYENV_ROOT}/global" -elif [ -e "${PYENV_ROOT}/default" ]; then - echo "${PYENV_ROOT}/default" +if [ -n "$target_dir" ]; then + find_local_version_file "$target_dir" else - echo "$global_version_file" + find_local_version_file "$PYENV_DIR" || { + [ "$PYENV_DIR" != "$PWD" ] && find_local_version_file "$PWD" + } || echo "${PYENV_ROOT}/version" fi diff --git a/libexec/pyenv-version-name b/libexec/pyenv-version-name index ac4f6b49..65a198a4 100755 --- a/libexec/pyenv-version-name +++ b/libexec/pyenv-version-name @@ -8,6 +8,13 @@ if [ -z "$PYENV_VERSION" ]; then PYENV_VERSION="$(pyenv-version-file-read "$PYENV_VERSION_FILE" || true)" fi +OLDIFS="$IFS" +IFS=$'\n' scripts=(`pyenv-hooks version-name`) +IFS="$OLDIFS" +for script in "${scripts[@]}"; do + source "$script" +done + if [ -z "$PYENV_VERSION" ] || [ "$PYENV_VERSION" = "system" ]; then echo "system" exit diff --git a/libexec/pyenv-version-origin b/libexec/pyenv-version-origin index 9f5259f9..1067a013 100755 --- a/libexec/pyenv-version-origin +++ b/libexec/pyenv-version-origin @@ -3,7 +3,18 @@ set -e [ -n "$PYENV_DEBUG" ] && set -x -if [ -n "$PYENV_VERSION" ]; then +unset PYENV_VERSION_ORIGIN + +OLDIFS="$IFS" +IFS=$'\n' scripts=(`pyenv-hooks version-origin`) +IFS="$OLDIFS" +for script in "${scripts[@]}"; do + source "$script" +done + +if [ -n "$PYENV_VERSION_ORIGIN" ]; then + echo "$PYENV_VERSION_ORIGIN" +elif [ -n "$PYENV_VERSION" ]; then echo "PYENV_VERSION environment variable" else pyenv-version-file diff --git a/libexec/pyenv-versions b/libexec/pyenv-versions index 6a422147..87363c7b 100755 --- a/libexec/pyenv-versions +++ b/libexec/pyenv-versions @@ -27,7 +27,7 @@ done versions_dir="${PYENV_ROOT}/versions" -if ! enable -f "${BASH_SOURCE%/*}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then +if ! enable -f "${BASH_SOURCE%/*}"/pyenv-realpath.dylib realpath 2>/dev/null; then if [ -n "$PYENV_NATIVE_EXT" ]; then echo "pyenv: failed to load \`realpath' builtin" >&2 exit 1 diff --git a/test/--version.bats b/test/--version.bats index c5410a74..9f2d5cff 100644 --- a/test/--version.bats +++ b/test/--version.bats @@ -2,22 +2,13 @@ load test_helper +export GIT_DIR="${PYENV_TEST_DIR}/.git" + 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" + cd "$PYENV_TEST_DIR" } git_commit() { @@ -28,47 +19,37 @@ git_commit() { assert [ ! -e "$PYENV_ROOT" ] run pyenv---version assert_success - [[ $output == "pyenv 20"* ]] + [[ $output == "ybenv "?.?.? ]] } @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"* ]] + [[ $output == "pyenv "?.?.? ]] } @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 tag v0.4.1 git_commit git_commit - cd "$PYENV_TEST_DIR" run pyenv---version - assert_success - [[ $output == "pyenv 20380119-2-g"* ]] + assert_success "pyenv 0.4.1-2-g$(git rev-parse --short HEAD)" } @test "prints default version if no tags in git repo" { - 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" run pyenv---version - [[ $output == "pyenv 20"* ]] + [[ $output == "pyenv "?.?.? ]] } diff --git a/test/exec.bats b/test/exec.bats index 43f6ab35..5f5f40fd 100644 --- a/test/exec.bats +++ b/test/exec.bats @@ -43,27 +43,14 @@ python OUT } -@test "supports hook path with spaces" { - hook_path="${PYENV_TEST_DIR}/custom stuff/pyenv hooks" - mkdir -p "${hook_path}/exec" - echo "export HELLO='from hook'" > "${hook_path}/exec/hello.bash" - - export PYENV_VERSION=system - PYENV_HOOK_PATH="$hook_path" run pyenv-exec env - assert_success - assert_line "HELLO=from hook" -} - @test "carries original IFS within hooks" { - hook_path="${PYENV_TEST_DIR}/pyenv.d" - mkdir -p "${hook_path}/exec" - cat > "${hook_path}/exec/hello.bash" <" @@ -15,11 +10,13 @@ create_hook() { @test "prints list of hooks" { path1="${PYENV_TEST_DIR}/pyenv.d" path2="${PYENV_TEST_DIR}/etc/pyenv_hooks" - create_hook "$path1" exec "hello.bash" - create_hook "$path1" exec "ahoy.bash" - create_hook "$path1" exec "invalid.sh" - create_hook "$path1" which "boom.bash" - create_hook "$path2" exec "bueno.bash" + PYENV_HOOK_PATH="$path1" + create_hook exec "hello.bash" + create_hook exec "ahoy.bash" + create_hook exec "invalid.sh" + create_hook which "boom.bash" + PYENV_HOOK_PATH="$path2" + create_hook exec "bueno.bash" PYENV_HOOK_PATH="$path1:$path2" run pyenv-hooks exec assert_success @@ -33,8 +30,10 @@ OUT @test "supports hook paths with spaces" { path1="${PYENV_TEST_DIR}/my hooks/pyenv.d" path2="${PYENV_TEST_DIR}/etc/pyenv hooks" - create_hook "$path1" exec "hello.bash" - create_hook "$path2" exec "ahoy.bash" + PYENV_HOOK_PATH="$path1" + create_hook exec "hello.bash" + PYENV_HOOK_PATH="$path2" + create_hook exec "ahoy.bash" PYENV_HOOK_PATH="$path1:$path2" run pyenv-hooks exec assert_success @@ -45,8 +44,8 @@ OUT } @test "resolves relative paths" { - path="${PYENV_TEST_DIR}/pyenv.d" - create_hook "$path" exec "hello.bash" + PYENV_HOOK_PATH="${PYENV_TEST_DIR}/pyenv.d" + create_hook exec "hello.bash" mkdir -p "$HOME" PYENV_HOOK_PATH="${HOME}/../pyenv.d" run pyenv-hooks exec diff --git a/test/init.bats b/test/init.bats index 5f39d9fa..bf0ccaab 100644 --- a/test/init.bats +++ b/test/init.bats @@ -25,12 +25,24 @@ load test_helper } @test "detect parent shell" { - root="$(cd $BATS_TEST_DIRNAME/.. && pwd)" SHELL=/bin/false run pyenv-init - assert_success assert_line "export PYENV_SHELL=bash" } +@test "detect parent shell from script" { + mkdir -p "$PYENV_TEST_DIR" + cd "$PYENV_TEST_DIR" + cat > myscript.sh < .pyenv-version - run pyenv-local - assert_success "1.2.3" -} - -@test "local .python-version has precedence over .pyenv-version" { - echo "2.7" > .pyenv-version - echo "3.4" > .python-version - run pyenv-local - assert_success "3.4" -} - -@test "ignores version in parent directory" { +@test "discovers version file in parent directory" { echo "1.2.3" > .python-version mkdir -p "subdir" && cd "subdir" run pyenv-local - assert_failure + assert_success "1.2.3" } @test "ignores PYENV_DIR" { @@ -64,40 +51,9 @@ setup() { assert [ "$(cat .python-version)" = "1.2.3" ] } -@test "renames .pyenv-version to .python-version" { - echo "2.7.6" > .pyenv-version - mkdir -p "${PYENV_ROOT}/versions/3.3.3" - run pyenv-local - assert_success "2.7.6" - run pyenv-local "3.3.3" - assert_success - assert_output < .pyenv-version - assert [ ! -e "${PYENV_ROOT}/versions/3.3.3" ] - run pyenv-local "3.3.3" - assert_failure "pyenv: version \`3.3.3' not installed" - assert [ ! -e .python-version ] - assert [ "$(cat .pyenv-version)" = "2.7.6" ] -} - @test "unsets local version" { touch .python-version run pyenv-local --unset assert_success "" - assert [ ! -e .pyenv-version ] -} - -@test "unsets alternate version file" { - touch .pyenv-version - run pyenv-local --unset - assert_success "" - assert [ ! -e .pyenv-version ] + assert [ ! -e .python-version ] } diff --git a/test/pyenv.bats b/test/pyenv.bats index 4fced1f1..d1610728 100644 --- a/test/pyenv.bats +++ b/test/pyenv.bats @@ -70,6 +70,7 @@ load test_helper } @test "PYENV_HOOK_PATH includes pyenv built-in plugins" { + unset PYENV_HOOK_PATH run pyenv echo "PYENV_HOOK_PATH" - assert_success ":${PYENV_ROOT}/pyenv.d:${BATS_TEST_DIRNAME%/*}/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks" + assert_success "${PYENV_ROOT}/pyenv.d:${BATS_TEST_DIRNAME%/*}/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks" } diff --git a/test/rehash.bats b/test/rehash.bats index ab049527..99612733 100755 --- a/test/rehash.bats +++ b/test/rehash.bats @@ -86,15 +86,13 @@ OUT } @test "carries original IFS within hooks" { - hook_path="${PYENV_TEST_DIR}/pyenv.d" - mkdir -p "${hook_path}/rehash" - cat > "${hook_path}/rehash/hello.bash" < "${PYENV_HOOK_PATH}/$1/$2" + fi +} diff --git a/test/version-file.bats b/test/version-file.bats index 8ecf5b8a..84ceba5d 100644 --- a/test/version-file.bats +++ b/test/version-file.bats @@ -12,6 +12,12 @@ create_file() { touch "$1" } +@test "detects global 'version' file" { + create_file "${PYENV_ROOT}/version" + run pyenv-version-file + assert_success "${PYENV_ROOT}/version" +} + @test "prints global file if no version files exist" { assert [ ! -e "${PYENV_ROOT}/version" ] assert [ ! -e ".python-version" ] @@ -19,45 +25,12 @@ create_file() { assert_success "${PYENV_ROOT}/version" } -@test "detects 'global' file" { - create_file "${PYENV_ROOT}/global" - run pyenv-version-file - assert_success "${PYENV_ROOT}/global" -} - -@test "detects 'default' file" { - create_file "${PYENV_ROOT}/default" - run pyenv-version-file - assert_success "${PYENV_ROOT}/default" -} - -@test "'version' has precedence over 'global' and 'default'" { - create_file "${PYENV_ROOT}/version" - create_file "${PYENV_ROOT}/global" - create_file "${PYENV_ROOT}/default" - run pyenv-version-file - assert_success "${PYENV_ROOT}/version" -} - @test "in current directory" { create_file ".python-version" run pyenv-version-file assert_success "${PYENV_TEST_DIR}/.python-version" } -@test "legacy file in current directory" { - create_file ".pyenv-version" - run pyenv-version-file - assert_success "${PYENV_TEST_DIR}/.pyenv-version" -} - -@test ".python-version has precedence over legacy file" { - create_file ".python-version" - create_file ".pyenv-version" - run pyenv-version-file - assert_success "${PYENV_TEST_DIR}/.python-version" -} - @test "in parent directory" { create_file ".python-version" mkdir -p project @@ -74,14 +47,6 @@ create_file() { assert_success "${PYENV_TEST_DIR}/project/.python-version" } -@test "legacy file has precedence if higher" { - create_file ".python-version" - create_file "project/.pyenv-version" - cd project - run pyenv-version-file - assert_success "${PYENV_TEST_DIR}/project/.pyenv-version" -} - @test "PYENV_DIR has precedence over PWD" { create_file "widget/.python-version" create_file "project/.python-version" @@ -97,3 +62,14 @@ create_file() { PYENV_DIR="${PYENV_TEST_DIR}/widget/blank" run pyenv-version-file assert_success "${PYENV_TEST_DIR}/project/.python-version" } + +@test "finds version file in target directory" { + create_file "project/.python-version" + run pyenv-version-file "${PWD}/project" + assert_success "${PYENV_TEST_DIR}/project/.python-version" +} + +@test "fails when no version file in target directory" { + run pyenv-version-file "$PWD" + assert_failure "" +} diff --git a/test/version-name.bats b/test/version-name.bats index b968bb75..e1d721b0 100644 --- a/test/version-name.bats +++ b/test/version-name.bats @@ -22,9 +22,30 @@ setup() { assert_success "system" } +@test "PYENV_VERSION can be overridden by hook" { + create_version "2.7.11" + create_version "3.5.1" + create_hook version-name test.bash <<<"PYENV_VERSION=3.5.1" + + PYENV_VERSION=2.7.11 run pyenv-version-name + assert_success "3.5.1" +} + +@test "carries original IFS within hooks" { + create_hook version-name hello.bash < ".python-version" <<<"2.7.6" run pyenv-version-name diff --git a/test/version-origin.bats b/test/version-origin.bats index 324b042e..be6d6f93 100644 --- a/test/version-origin.bats +++ b/test/version-origin.bats @@ -31,8 +31,26 @@ setup() { assert_success "${PWD}/.python-version" } -@test "detects alternate version file" { - touch .pyenv-version - run pyenv-version-origin - assert_success "${PWD}/.pyenv-version" +@test "reports from hook" { + create_hook version-origin test.bash <<<"PYENV_VERSION_ORIGIN=plugin" + + PYENV_VERSION=1 run pyenv-version-origin + assert_success "plugin" +} + +@test "carries original IFS within hooks" { + create_hook version-origin hello.bash < "${hook_path}/which/hello.bash" <