From 1e60c98e4a8e7a31c3329c738631b04fdd607dfe Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 13:59:42 +0100 Subject: [PATCH 01/10] Use escaped grep command, skipping aliases --- autoenv.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoenv.zsh b/autoenv.zsh index ff8b4a7..bb001aa 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -24,7 +24,7 @@ _dotenv_authorize() { _dotenv_deauthorize() { env_file=$1 - echo $(grep -Gv $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE + echo $(\grep -Gv $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE } _dotenv_print_unauthorized_message() { From 41625bf31cb960e8e86ce03bc013cad14fdf96e8 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 14:49:48 +0100 Subject: [PATCH 02/10] Use 'local' in functions --- autoenv.zsh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/autoenv.zsh b/autoenv.zsh index bb001aa..51ccca0 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -4,26 +4,26 @@ export ENV_AUTHORIZATION_FILE=$HOME/.env_auth _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) + local env_file=$1 + local pair=$(_dotenv_hash_pair $env_file) touch $ENV_AUTHORIZATION_FILE \grep -Gq $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 + local env_file=$1 echo $(\grep -Gv $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE } From 4676713bc6539f5bdf3a26cb21b6091ddf8b1451 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 15:08:52 +0100 Subject: [PATCH 03/10] Various improvements - Support for leave event, via DOTENV_FILE_LEAVE and _dotenv_event (can use DOTENV_FILE_LEAVE=$DOTENV_FILE_ENTER). - Support for searching upwards for $DOTENV_FILE_ENTER (#3). - Source .env only once per session, but re-source when leave events are enabled (#1). - Trigger the machinery when the script gets sourced, for the current dir (#2). --- autoenv.zsh | 95 +++++++++++++++++++++++++++++++++++++++---------- tests/autoenv.t | 47 +++++++++++++----------- tests/leave.t | 80 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 39 deletions(-) create mode 100644 tests/leave.t diff --git a/autoenv.zsh b/autoenv.zsh index 51ccca0..313378b 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -1,8 +1,19 @@ # Stolen from # https://github.com/joshuaclayton/dotfiles/blob/master/zsh_profile.d/autoenv.zsh +# TODO: move this to DOTENV_*?! export ENV_AUTHORIZATION_FILE=$HOME/.env_auth +: ${DOTENV_FILE_ENTER:=.env} +: ${DOTENV_FILE_LEAVE:=.env.leave} + +# Look for .env in parent dirs? +: ${DOTENV_LOOK_UPWARDS:=0} + +# Handle leave events, when leaving ? +: ${DOTENV_HANDLE_LEAVE:=1} + + _dotenv_hash_pair() { local env_file=$1 env_shasum=$(shasum $env_file | cut -d' ' -f1) @@ -28,7 +39,7 @@ _dotenv_deauthorize() { } _dotenv_print_unauthorized_message() { - echo "Attempting to load unauthorized env: $1" + echo "Attempting to load unauthorized env file: $1" echo "" echo "**********************************************" echo "" @@ -36,35 +47,83 @@ _dotenv_print_unauthorized_message() { echo "" echo "**********************************************" echo "" - echo "Would you like to authorize it? (y/n)" + echo -n "Would you like to authorize it? [y/N] " } # 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" +_dotenv_check_authorized_env_file() { + if ! _dotenv_authorized_env_file $1; then + _dotenv_print_unauthorized_message $1 - 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_stack_entered=() - if [[ $answer == 'y' ]] - then - _dotenv_authorize $env_file - source $env_file +_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_event=leave + source $env_file_leave + unset _dotenv_event + 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 + setopt localoptions extendedglob + local m + m=((../)#${DOTENV_FILE_ENTER}(N)) + if (( $#m )); then + env_file=$m[1] fi fi + + if ! [[ -f $env_file ]]; then + return + fi + + if ! _dotenv_check_authorized_env_file $env_file; then + return + fi + + # Load the env file only once. + if (( ${+_dotenv_stack_entered[(r)${env_file:A:h}]} )); then + return + fi + + _dotenv_stack_entered+=(${env_file:A:h}) + + _dotenv_event=enter + source $env_file + unset _dotenv_event } -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..2954878 --- /dev/null +++ b/tests/leave.t @@ -0,0 +1,80 @@ +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 From 1d3e5b69a7ed2ec112f47f6b684534c727a02fa7 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 15:25:20 +0100 Subject: [PATCH 04/10] Add a test for DOTENV_HANDLE_LEAVE=0 --- tests/leave.t | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/leave.t b/tests/leave.t index 2954878..770b310 100644 --- a/tests/leave.t +++ b/tests/leave.t @@ -78,3 +78,13 @@ Now check with subdirs, not looking at parent dirs. $ 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 From 4662727a22d4235d54377d53f838ef373cf51206 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 15:48:57 +0100 Subject: [PATCH 05/10] Cleanup, and fixes (absolute path for env_file) --- autoenv.zsh | 55 ++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/autoenv.zsh b/autoenv.zsh index 313378b..265482b 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -1,19 +1,28 @@ -# 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 leaving ? +# 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() { local env_file=$1 env_shasum=$(shasum $env_file | cut -d' ' -f1) @@ -23,8 +32,8 @@ _dotenv_hash_pair() { _dotenv_authorized_env_file() { local env_file=$1 local pair=$(_dotenv_hash_pair $env_file) - touch $ENV_AUTHORIZATION_FILE - \grep -Gq $pair $ENV_AUTHORIZATION_FILE + test -f $ENV_AUTHORIZATION_FILE \ + && \grep -qF $pair $ENV_AUTHORIZATION_FILE } _dotenv_authorize() { @@ -35,19 +44,9 @@ _dotenv_authorize() { _dotenv_deauthorize() { local env_file=$1 - echo $(\grep -Gv $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE -} - -_dotenv_print_unauthorized_message() { - 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_AUTHORIZATION_FILE ]]; then + echo $(\grep -vF $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE + fi } # This function can be mocked in tests @@ -59,7 +58,15 @@ _dotenv_read_answer() { _dotenv_check_authorized_env_file() { if ! _dotenv_authorized_env_file $1; then - _dotenv_print_unauthorized_message $1 + 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] " local answer=$(_dotenv_read_answer) echo @@ -72,8 +79,6 @@ _dotenv_check_authorized_env_file() { return 0 } -_dotenv_stack_entered=() - _dotenv_chpwd_handler() { local env_file="$PWD/$DOTENV_FILE_ENTER" @@ -98,15 +103,13 @@ _dotenv_chpwd_handler() { local m m=((../)#${DOTENV_FILE_ENTER}(N)) if (( $#m )); then - env_file=$m[1] + env_file=${${m[1]}:A} + else + return fi fi - if ! [[ -f $env_file ]]; then - return - fi - - if ! _dotenv_check_authorized_env_file $env_file; then + if ! [[ -f $env_file ]] || ! _dotenv_check_authorized_env_file $env_file; then return fi From d114d0a0ea09c4ad53c244e7c1f6b31c47f0d14e Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 16:09:02 +0100 Subject: [PATCH 06/10] Check for file existence in _dotenv_check_authorized_env_file --- autoenv.zsh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autoenv.zsh b/autoenv.zsh index 265482b..fe1fa87 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -57,6 +57,9 @@ _dotenv_read_answer() { } _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 "" @@ -109,7 +112,7 @@ _dotenv_chpwd_handler() { fi fi - if ! [[ -f $env_file ]] || ! _dotenv_check_authorized_env_file $env_file; then + if ! _dotenv_check_authorized_env_file $env_file; then return fi From a37b0bc3f500c062dedd5881a788d97743bdfa0a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 16:09:37 +0100 Subject: [PATCH 07/10] Wrap sourcing in _dotenv_source: chdir and set _dotenv_cwd=$PWD --- autoenv.zsh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/autoenv.zsh b/autoenv.zsh index fe1fa87..4fcde08 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -82,6 +82,18 @@ _dotenv_check_authorized_env_file() { return 0 } +_dotenv_source() { + local env_file=$1 + _dotenv_event=$2 + _dotenv_cwd=$PWD + + cd -q ${env_file:h} + source $env_file + cd -q - + + unset _dotenv_event _dotenv_cwd +} + _dotenv_chpwd_handler() { local env_file="$PWD/$DOTENV_FILE_ENTER" @@ -91,9 +103,7 @@ _dotenv_chpwd_handler() { 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_event=leave - source $env_file_leave - unset _dotenv_event + _dotenv_source $env_file_leave leave fi # Remove this entry from the stack. _dotenv_stack_entered=(${_dotenv_stack_entered#$prev_dir}) @@ -123,9 +133,7 @@ _dotenv_chpwd_handler() { _dotenv_stack_entered+=(${env_file:A:h}) - _dotenv_event=enter - source $env_file - unset _dotenv_event + _dotenv_source $env_file enter } autoload -U add-zsh-hook From 71ed15679b24f74385010bd710780811deed4ab0 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 17:04:36 +0100 Subject: [PATCH 08/10] _dotenv_source: use 'builtin cd' and stored dir instead of 'cd -' --- autoenv.zsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoenv.zsh b/autoenv.zsh index 4fcde08..a09e57b 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -87,9 +87,9 @@ _dotenv_source() { _dotenv_event=$2 _dotenv_cwd=$PWD - cd -q ${env_file:h} + builtin cd -q ${env_file:h} source $env_file - cd -q - + builtin cd -q $_dotenv_cwd unset _dotenv_event _dotenv_cwd } From 64bbb2f305b1819727c35f75b759c3c2dfe49163 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 16 Nov 2014 11:39:10 +0100 Subject: [PATCH 09/10] doc / small refactoring according to feedback --- autoenv.zsh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/autoenv.zsh b/autoenv.zsh index a09e57b..723fe60 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -56,6 +56,7 @@ _dotenv_read_answer() { echo $answer } +# Args: 1: absolute path to env file (resolved symlinks). _dotenv_check_authorized_env_file() { if ! [[ -f $1 ]]; then return 1 @@ -112,6 +113,7 @@ _dotenv_chpwd_handler() { 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)) @@ -126,12 +128,14 @@ _dotenv_chpwd_handler() { return fi - # Load the env file only once. - if (( ${+_dotenv_stack_entered[(r)${env_file:A:h}]} )); then + # 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:A:h}) + _dotenv_stack_entered+=(${env_file_dir}) _dotenv_source $env_file enter } From 98f0ec81feee458c9a112993cbc4c76fb2625203 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 16 Nov 2014 11:40:09 +0100 Subject: [PATCH 10/10] Use 'test' as target for tests (common sense) --- .travis.yml | 2 +- Makefile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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