diff --git a/.travis.yml b/.travis.yml index b56caf2..00828ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ python: before_script: - "sudo apt-get install zsh" install: "sudo pip install cram" -script: "make tests" +script: "make test" diff --git a/Makefile b/Makefile index bed40b1..a769734 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -.PHONY: itests tests +.PHONY: itest test -itests: +itest: ZDOTDIR="${PWD}/tests" cram -i --shell=zsh tests -tests: +test: ZDOTDIR="${PWD}/tests" cram --shell=zsh tests diff --git a/autoenv.zsh b/autoenv.zsh index ff8b4a7..723fe60 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -1,70 +1,147 @@ -# Stolen from +# Initially based on # https://github.com/joshuaclayton/dotfiles/blob/master/zsh_profile.d/autoenv.zsh +# TODO: move this to DOTENV_*?! export ENV_AUTHORIZATION_FILE=$HOME/.env_auth +# Name of file to look for when entering directories. +: ${DOTENV_FILE_ENTER:=.env} + +# Name of file to look for when leaving directories. +# Requires DOTENV_HANDLE_LEAVE=1. +: ${DOTENV_FILE_LEAVE:=.env.leave} + +# Look for .env in parent dirs? +: ${DOTENV_LOOK_UPWARDS:=0} + +# Handle leave events when changing away from a subtree, where an "enter" +# event was handled? +: ${DOTENV_HANDLE_LEAVE:=1} + + +# Internal: stack of entered (and handled) directories. +_dotenv_stack_entered=() + + _dotenv_hash_pair() { - env_file=$1 + local env_file=$1 env_shasum=$(shasum $env_file | cut -d' ' -f1) echo "$env_file:$env_shasum" } _dotenv_authorized_env_file() { - env_file=$1 - pair=$(_dotenv_hash_pair $env_file) - touch $ENV_AUTHORIZATION_FILE - \grep -Gq $pair $ENV_AUTHORIZATION_FILE + local env_file=$1 + local pair=$(_dotenv_hash_pair $env_file) + test -f $ENV_AUTHORIZATION_FILE \ + && \grep -qF $pair $ENV_AUTHORIZATION_FILE } _dotenv_authorize() { - env_file=$1 + local env_file=$1 _dotenv_deauthorize $env_file _dotenv_hash_pair $env_file >> $ENV_AUTHORIZATION_FILE } _dotenv_deauthorize() { - env_file=$1 - echo $(grep -Gv $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE -} - -_dotenv_print_unauthorized_message() { - echo "Attempting to load unauthorized env: $1" - echo "" - echo "**********************************************" - echo "" - cat $1 - echo "" - echo "**********************************************" - echo "" - echo "Would you like to authorize it? (y/n)" + local env_file=$1 + if [[ -f $ENV_AUTHORIZATION_FILE ]]; then + echo $(\grep -vF $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE + fi } # This function can be mocked in tests _dotenv_read_answer() { - read answer + local answer + read -q answer + echo $answer } -_dotenv_source_env() { - local env_file="$PWD/.env" +# Args: 1: absolute path to env file (resolved symlinks). +_dotenv_check_authorized_env_file() { + if ! [[ -f $1 ]]; then + return 1 + fi + if ! _dotenv_authorized_env_file $1; then + echo "Attempting to load unauthorized env file: $1" + echo "" + echo "**********************************************" + echo "" + cat $1 + echo "" + echo "**********************************************" + echo "" + echo -n "Would you like to authorize it? [y/N] " - if [[ -f $env_file ]] - then - if _dotenv_authorized_env_file $env_file - then - source $env_file - return 0 + local answer=$(_dotenv_read_answer) + echo + if [[ $answer != 'y' ]]; then + return 1 fi - _dotenv_print_unauthorized_message $env_file + _dotenv_authorize $1 + fi + return 0 +} - _dotenv_read_answer +_dotenv_source() { + local env_file=$1 + _dotenv_event=$2 + _dotenv_cwd=$PWD - if [[ $answer == 'y' ]] - then - _dotenv_authorize $env_file - source $env_file + builtin cd -q ${env_file:h} + source $env_file + builtin cd -q $_dotenv_cwd + + unset _dotenv_event _dotenv_cwd +} + +_dotenv_chpwd_handler() { + local env_file="$PWD/$DOTENV_FILE_ENTER" + + # Handle leave event for previously sourced env files. + if [[ $DOTENV_HANDLE_LEAVE == 1 ]] && (( $#_dotenv_stack_entered )); then + for prev_dir in ${_dotenv_stack_entered}; do + if ! [[ ${PWD}/ == ${prev_dir}/* ]]; then + local env_file_leave=$prev_dir/$DOTENV_FILE_LEAVE + if _dotenv_check_authorized_env_file $env_file_leave; then + _dotenv_source $env_file_leave leave + fi + # Remove this entry from the stack. + _dotenv_stack_entered=(${_dotenv_stack_entered#$prev_dir}) + fi + done + fi + + if ! [[ -f $env_file ]] && [[ $DOTENV_LOOK_UPWARDS == 1 ]]; then + # Look for files in parent dirs, using an extended Zsh glob. + setopt localoptions extendedglob + local m + m=((../)#${DOTENV_FILE_ENTER}(N)) + if (( $#m )); then + env_file=${${m[1]}:A} + else + return fi fi + + if ! _dotenv_check_authorized_env_file $env_file; then + return + fi + + # Load the env file only once: check if $env_file's parent + # is in $_dotenv_stack_entered. + local env_file_dir=${env_file:A:h} + if (( ${+_dotenv_stack_entered[(r)${env_file_dir}]} )); then + return + fi + + _dotenv_stack_entered+=(${env_file_dir}) + + _dotenv_source $env_file enter } -chpwd_functions=($chpwd_functions _dotenv_source_env) +autoload -U add-zsh-hook +add-zsh-hook chpwd _dotenv_chpwd_handler + +# Look in current directory already. +_dotenv_chpwd_handler diff --git a/tests/autoenv.t b/tests/autoenv.t index c4f1f41..f3ede0c 100644 --- a/tests/autoenv.t +++ b/tests/autoenv.t @@ -4,53 +4,57 @@ Ensure we have our mocked out ENV_AUTHORIZATION_FILE Lets set a simple .env action - $ echo 'echo blah' >> .env + $ echo 'echo ENTERED' >> .env Manually create auth file - $ echo "$PWD/.env:$(echo echo blah | shasum)" > $ENV_AUTHORIZATION_FILE + $ echo "$PWD/.env:$(echo echo ENTERED | shasum)" > $ENV_AUTHORIZATION_FILE $ cd . - blah + ENTERED Now try to make it accept it + $ unset _dotenv_stack_entered $ rm $ENV_AUTHORIZATION_FILE - $ _dotenv_read_answer() { answer='y' } + $ _dotenv_read_answer() { echo 'y' } $ cd . - Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) ********************************************** - echo blah + echo ENTERED ********************************************** - Would you like to authorize it? (y/n) - blah + Would you like to authorize it? [y/N] + ENTERED -The last "blah" is because it executed the command +The last "ENTERED" is because it executed the command Now lets see that it actually checks the shasum value + $ unset _dotenv_stack_entered $ cd . - blah + ENTERED + + $ unset _dotenv_stack_entered $ rm $ENV_AUTHORIZATION_FILE $ echo "$PWD/.env:$(echo mischief | shasum)" > $ENV_AUTHORIZATION_FILE $ cd . - Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) ********************************************** - echo blah + echo ENTERED ********************************************** - Would you like to authorize it? (y/n) - blah + Would you like to authorize it? [y/N] + ENTERED @@ -58,18 +62,19 @@ Now lets see that it actually checks the shasum value Now, will it take no for an answer? + $ unset _dotenv_stack_entered $ rm $ENV_AUTHORIZATION_FILE - $ _dotenv_read_answer() { answer='n' } + $ _dotenv_read_answer() { echo 'n' } $ cd . - Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) ********************************************** - echo blah + echo ENTERED ********************************************** - Would you like to authorize it? (y/n) + Would you like to authorize it? [y/N] @@ -78,12 +83,12 @@ Now, will it take no for an answer? Lets also try one more time to ensure it didnt add it $ cd . - Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) ********************************************** - echo blah + echo ENTERED ********************************************** - Would you like to authorize it? (y/n) + Would you like to authorize it? [y/N] diff --git a/tests/leave.t b/tests/leave.t new file mode 100644 index 0000000..770b310 --- /dev/null +++ b/tests/leave.t @@ -0,0 +1,90 @@ +Ensure we have our mocked out ENV_AUTHORIZATION_FILE + + $ [[ $ENV_AUTHORIZATION_FILE[0,4] == '/tmp' ]] || return 1 + + +Lets set a simple .env action + + $ mkdir sub + $ cd sub + $ echo 'echo ENTERED' >> .env + $ echo 'echo LEFT' >> .env.leave + +Change to the directory. + + $ _dotenv_read_answer() { echo 'y' } + $ cd . + Attempting to load unauthorized env file: /tmp/cramtests-??????/leave.t/sub/.env (glob) + + ********************************************** + + echo ENTERED + + ********************************************** + + Would you like to authorize it? [y/N] + ENTERED + + +Leave the directory and answer "no". + + $ _dotenv_read_answer() { echo 'n' } + $ cd .. + Attempting to load unauthorized env file: /tmp/cramtests-??????/leave.t/sub/.env.leave (glob) + + ********************************************** + + echo LEFT + + ********************************************** + + Would you like to authorize it? [y/N] + + + $ cd sub + ENTERED + $ _dotenv_read_answer() { echo 'y' } + $ cd .. + Attempting to load unauthorized env file: /tmp/cramtests-??????/leave.t/sub/.env.leave (glob) + + ********************************************** + + echo LEFT + + ********************************************** + + Would you like to authorize it? [y/N] + LEFT + + +Now check with subdirs, looking upwards. + + $ DOTENV_LOOK_UPWARDS=1 + $ mkdir sub/child + $ cd sub/child + ENTERED + $ cd . + $ cd .. + $ cd .. + LEFT + + +Now check with subdirs, not looking at parent dirs. + + $ DOTENV_LOOK_UPWARDS=0 + $ cd sub/child + $ cd .. + ENTERED + $ cd child + $ cd ../.. + LEFT + + +Test that .env is sourced only once with DOTENV_HANDLE_LEAVE=0. + + $ unset _dotenv_stack_entered + $ DOTENV_HANDLE_LEAVE=0 + $ cd sub + ENTERED + $ cd .. + $ cd sub