diff --git a/Makefile b/Makefile index a769734..0ffe150 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,17 @@ .PHONY: itest test +test: + ZDOTDIR="${PWD}/tests" cram --shell=zsh -v tests + itest: ZDOTDIR="${PWD}/tests" cram -i --shell=zsh tests -test: - ZDOTDIR="${PWD}/tests" cram --shell=zsh tests +# Define targets for test files, with relative and abolute path. +# Use verbose output, which is useful with Vim's 'errorformat'. +TESTS:=$(wildcard tests/*.t) + +uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1))) +_TESTS_REL_AND_ABS:=$(call uniq,$(abspath $(TESTS)) $(TESTS)) +$(_TESTS_REL_AND_ABS): + ZDOTDIR="${PWD}/tests" cram --shell=zsh -v $@ +.PHONY: $(_TESTS_REL_AND_ABS) diff --git a/autoenv.zsh b/autoenv.zsh index 60c8d03..f770d8a 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -1,68 +1,177 @@ # 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 +export AUTOENV_ENV_FILENAME=$HOME/.env_auth # Name of file to look for when entering directories. -: ${DOTENV_FILE_ENTER:=.env} +: ${AUTOENV_FILE_ENTER:=.env} # Name of file to look for when leaving directories. -# Requires DOTENV_HANDLE_LEAVE=1. -: ${DOTENV_FILE_LEAVE:=.env.leave} +# Requires AUTOENV_HANDLE_LEAVE=1. +: ${AUTOENV_FILE_LEAVE:=.env.leave} # Look for .env in parent dirs? -: ${DOTENV_LOOK_UPWARDS:=1} +: ${AUTOENV_LOOK_UPWARDS:=1} # Handle leave events when changing away from a subtree, where an "enter" # event was handled? -: ${DOTENV_HANDLE_LEAVE:=1} +: ${AUTOENV_HANDLE_LEAVE:=1} +# Enable debugging. Multiple levels are supported (max 2). +: ${AUTOENV_DEBUG:=0} -# Internal: stack of entered (and handled) directories. -_dotenv_stack_entered=() +# Public helper functions, which can be used from your .env files: +# +# Source the next .env file from parent directories. +# This is useful if you want to use a base .env file for a directory subtree. +autoenv_source_parent() { + local parent_env_file=$(_autoenv_get_file_upwards $PWD) + if [[ -n $parent_env_file ]] \ + && _autoenv_check_authorized_env_file $parent_env_file; then + _autoenv_debug "Calling autoenv_source_parent: parent_env_file:$parent_env_file" -_dotenv_hash_pair() { - local env_file=$1 - env_shasum=$(shasum $env_file | cut -d' ' -f1) - echo "$env_file:$env_shasum" + local parent_env_dir=${parent_env_file:A:h} + + _autoenv_stack_entered_add $parent_env_file + + _autoenv_source $parent_env_file enter $parent_env_dir + fi } -_dotenv_authorized_env_file() { +# Internal functions. {{{ +# Internal: stack of entered (and handled) directories. {{{ +_autoenv_stack_entered=() +typeset -A _autoenv_stack_entered_mtime +_autoenv_stack_entered_mtime=() + +# Add an entry to the stack, and remember its mtime. +_autoenv_stack_entered_add() { local env_file=$1 - local pair=$(_dotenv_hash_pair $env_file) - test -f $ENV_AUTHORIZATION_FILE \ - && \grep -qF $pair $ENV_AUTHORIZATION_FILE + + _autoenv_debug "[stack] adding: $env_file" 2 + + # Remove any existing entry. + _autoenv_stack_entered_remove $env_file + + # Append it to the stack, and remember its mtime. + _autoenv_stack_entered+=($env_file) + _autoenv_stack_entered_mtime[$env_file]=$(_autoenv_get_file_mtime $env_file) } -_dotenv_authorize() { - local env_file=$1 - _dotenv_deauthorize $env_file - _dotenv_hash_pair $env_file >> $ENV_AUTHORIZATION_FILE +_autoenv_get_file_mtime() { + if [[ -f $1 ]]; then + zstat +mtime $1 + else + echo 0 + fi } -_dotenv_deauthorize() { +# Remove an entry from the stack. +_autoenv_stack_entered_remove() { local env_file=$1 - if [[ -f $ENV_AUTHORIZATION_FILE ]]; then - echo $(\grep -vF $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE + _autoenv_debug "[stack] removing: $env_file" 2 + _autoenv_stack_entered[$_autoenv_stack_entered[(i)$env_file]]=() + _autoenv_stack_entered_mtime[$env_file]= +} + +# Is the given entry already in the stack? +_autoenv_stack_entered_contains() { + local env_file=$1 + if (( ${+_autoenv_stack_entered[(r)${env_file}]} )); then + # Entry is in stack. + if [[ $_autoenv_stack_entered_mtime[$env_file] == $(_autoenv_get_file_mtime $env_file) ]]; then + # Entry has the expected mtime. + return + fi + fi + return 1 +} +# }}} + +# Internal function for debug output. {{{ +_autoenv_debug() { + local msg=$1 + local level=${2:-1} + if [[ $AUTOENV_DEBUG -lt $level ]]; then + return + fi + # Load zsh color support. + if [[ -z $colors ]]; then + autoload colors + colors + fi + # Build $indent prefix. + local indent= + if [[ $_autoenv_debug_indent -gt 0 ]]; then + for i in {1..${_autoenv_debug_indent}}; do + indent=" $indent" + done + fi + + # Split $msg by \n (not newline). + lines=(${(ps:\\n:)msg}) + for line in $lines; do + echo -n "${fg_bold[blue]}[autoenv]${fg_no_bold[default]} " >&2 + echo ${indent}${line} >&2 + done +} +# }}} + +# Load zstat module, but only its builtin `zstat`. +zmodload -F zsh/stat b:zstat + + +_autoenv_hash_pair() { + local env_file=${1:A} + local env_shasum + if [[ -n $2 ]]; then + env_shasum=$2 + else + env_shasum=$(shasum $env_file | cut -d' ' -f1) + fi + echo "$env_file:$env_shasum:1" +} + +_autoenv_authorized_env_file() { + local env_file=$1 + local pair=$(_autoenv_hash_pair $env_file) + test -f $AUTOENV_ENV_FILENAME \ + && \grep -qF $pair $AUTOENV_ENV_FILENAME +} + +_autoenv_authorize() { + local env_file=$1 + _autoenv_deauthorize $env_file + _autoenv_hash_pair $env_file >> $AUTOENV_ENV_FILENAME +} + +_autoenv_deauthorize() { + local env_file=$1 + if [[ -f $AUTOENV_ENV_FILENAME ]]; then + echo $(\grep -vF $env_file $AUTOENV_ENV_FILENAME) > $AUTOENV_ENV_FILENAME fi } # This function can be mocked in tests -_dotenv_read_answer() { +_autoenv_ask_for_yes() { local answer - read -q answer - echo $answer + read answer + if [[ $answer == "yes" ]]; then + return 0 + else + return 1 + fi } # Args: 1: absolute path to env file (resolved symlinks). -_dotenv_check_authorized_env_file() { +_autoenv_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" + if ! _autoenv_authorized_env_file $1; then + echo "Attempting to load unauthorized env file!" + command ls -l $1 echo "" echo "**********************************************" echo "" @@ -70,78 +179,133 @@ _dotenv_check_authorized_env_file() { echo "" echo "**********************************************" echo "" - echo -n "Would you like to authorize it? [y/N] " + echo -n "Would you like to authorize it? (type 'yes') " - local answer=$(_dotenv_read_answer) - echo - if [[ $answer != 'y' ]]; then + if ! _autoenv_ask_for_yes; then return 1 fi - _dotenv_authorize $1 + _autoenv_authorize $1 fi return 0 } -_dotenv_source() { +# Get directory of this file (absolute, with resolved symlinks). +_autoenv_source_dir=${0:A:h} + +_autoenv_source() { local env_file=$1 - _dotenv_event=$2 - _dotenv_cwd=$PWD + _autoenv_event=$2 + local _autoenv_envfile_dir=${3:-${1:A:h}} - builtin cd -q ${env_file:h} + _autoenv_from_dir=$_autoenv_chpwd_prev_dir + _autoenv_to_dir=$PWD + + # Source varstash library once. + if [[ -z "$functions[(I)autostash]" ]]; then + source $_autoenv_source_dir/lib/varstash + # NOTE: Varstash uses $PWD as default for varstash_dir, we might set it to + # ${env_file:h}. + fi + + # Change to directory of env file, source it and cd back. + local new_dir=$PWD + builtin cd -q $_autoenv_envfile_dir + _autoenv_debug "== SOURCE: ${bold_color}$env_file${reset_color}\n PWD: $PWD" + (( _autoenv_debug_indent++ )) source $env_file - builtin cd -q $_dotenv_cwd + (( _autoenv_debug_indent-- )) + _autoenv_debug "== END SOURCE ==" + builtin cd -q $new_dir - unset _dotenv_event _dotenv_cwd + # Unset vars set for enter/leave scripts. + # This should not get done for recursion (via autoenv_source_parent), + # and can be useful to have in general after autoenv was used. + # unset _autoenv_event _autoenv_from_dir _autoenv_to_dir } -_dotenv_chpwd_handler() { - local env_file="$PWD/$DOTENV_FILE_ENTER" +_autoenv_get_file_upwards() { + local look_from=${1:-$PWD} + local look_for=${2:-$AUTOENV_FILE_ENTER} + + # Manually look in parent dirs. An extended Zsh glob should use Y1 for + # performance reasons, which is only available in zsh-5.0.5-146-g9381bb6. + local last + local parent_dir="$look_from/.." + while true; do + parent_dir=${parent_dir:A} + if [[ $parent_dir == $last ]]; then + break + fi + parent_file="${parent_dir}/${look_for}" + + if [[ -f $parent_file ]]; then + echo $parent_file + break + fi + + last=$parent_dir + parent_dir="${parent_dir}/.." + done +} + + +_autoenv_chpwd_prev_dir=$PWD +_autoenv_chpwd_handler() { + local env_file="$PWD/$AUTOENV_FILE_ENTER" + + _autoenv_debug "Calling chpwd handler: PWD=$PWD" # 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 [[ $AUTOENV_HANDLE_LEAVE == 1 ]] && (( $#_autoenv_stack_entered )); then + local prev_file prev_dir + for prev_file in ${_autoenv_stack_entered}; do + prev_dir=${prev_file:A:h} 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 + local env_file_leave=$prev_dir/$AUTOENV_FILE_LEAVE + if _autoenv_check_authorized_env_file $env_file_leave; then + _autoenv_source $env_file_leave leave $prev_dir fi - # Remove this entry from the stack. - _dotenv_stack_entered=(${_dotenv_stack_entered#$prev_dir}) + _autoenv_stack_entered_remove $prev_file 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 + if ! [[ -f $env_file ]] && [[ $AUTOENV_LOOK_UPWARDS == 1 ]]; then + env_file=$(_autoenv_get_file_upwards $PWD) + if [[ -z $env_file ]]; then + _autoenv_chpwd_prev_dir=$PWD return fi fi - if ! _dotenv_check_authorized_env_file $env_file; then + # Load the env file only once: check if $env_file is in the stack of entered + # directories. + if _autoenv_stack_entered_contains $env_file; then + _autoenv_debug "Already in stack: $env_file" + _autoenv_chpwd_prev_dir=$PWD 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 + if ! _autoenv_check_authorized_env_file $env_file; then + _autoenv_chpwd_prev_dir=$PWD return fi - _dotenv_stack_entered+=(${env_file_dir}) + _autoenv_stack_entered_add $env_file - _dotenv_source $env_file enter + # Source the enter env file. + _autoenv_debug "Sourcing from chpwd handler: $env_file" + _autoenv_source $env_file enter + + _autoenv_chpwd_prev_dir=$PWD + + (( _autoenv_debug_indent++ )) } +# }}} autoload -U add-zsh-hook -add-zsh-hook chpwd _dotenv_chpwd_handler +add-zsh-hook chpwd _autoenv_chpwd_handler # Look in current directory already. -_dotenv_chpwd_handler +_autoenv_chpwd_handler diff --git a/lib/varstash b/lib/varstash new file mode 100644 index 0000000..9844c32 --- /dev/null +++ b/lib/varstash @@ -0,0 +1,343 @@ +################################################################################ +# Stash/unstash support for per-directory variables +# +# Adopted for zsh-autoenv. +# +# Copyright (c) 2009,2012 Dave Olszewski +# http://github.com/cxreg/smartcd +# +# This code is released under GPL v2 and the Artistic License, and +# may be redistributed under the terms of either. +# +# +# This library allows you to save the current value of a given environment +# variable in a temporary location, so that you can modify it, and then +# later restore its original value. +# +# Note that you will need to be in the same directory you were in when you +# stashed in order to successfully unstash. This is because the temporary +# variable is derived from your current working directory's path. +# +# Usage: +# stash PATH +# export PATH=/something/else +# [...] +# unstash PATH +# +# Note that this was written for use with, and works very well with, +# smartcd. See the documentation there for examples. +# +# An alternate usage is `autostash' which will trigger autounstash when +# leaving the directory, if combined with smartcd. This reduces the amount +# of explicit configuration you need to provide: +# +# autostash PATH +# export PATH=/something/else +# +# You may also do both operations on line line, leaving only the very succinct +# +# autostash PATH=/something/else +# +# If you attempt to stash the same value twice, a warning will be displayed +# and the second stash will not occur. To make it happen anyway, pass -f +# as the first argument to stash. +# +# $ stash FOO +# $ stash FOO +# You have already stashed FOO, please specify "-f" if you want to overwrite another stashed value +# $ stash -f FOO +# $ +# +# This rule is a bit different if you are assigning a value and the variable +# has already been stashed. In that case, the new value will be assigned, but +# the stash will not be overwritten. This allows for non-conflicting chained +# stash-assign rules. +# +################################################################################ + + +# Library functions, from smartcd's lib/core/arrays. {{{ +function apush() { + local var=$1; shift + eval "$var=(\${$var[@]} \"\$@\")" +} + +function alen() { + local var=$1 + + if [[ -n $var ]]; then + eval "echo \${#$var[@]}" + fi +} + +function afirst() { + setopt localoptions && setopt ksharrays + local var=$1 + + if [[ -n $var ]] && (( $(eval "echo \${#$var[@]}") >= 1 )); then + eval "echo \"\${$var""[0]}\"" + fi +} + +function ashift() { + setopt localoptions && setopt ksharrays + local var=$1 + + local _ashift_return= + + if [[ -n $var ]] && (( $(eval "echo \${#$var[@]}") >= 1 )); then + eval "_ashift_return=\"\${$var""[0]}\"" + eval "$var""[0]=()" + + echo "$_ashift_return" + fi +} +# }}} + + +function stash() { + if [[ $1 == "-f" ]]; then + local force=1; shift + fi + + while [[ -n $1 ]]; do + if [[ $1 == "alias" && $2 =~ "=" ]]; then + shift + local _stashing_alias_assign=1 + continue + fi + + local stash_expression=$1 + local stash_which=${stash_expression%%'='*} + local stash_name=$(_mangle_var $stash_which) + + # Extract the value and make it double-quote safe + local stash_value=${stash_expression#*'='} + stash_value=${stash_value//\\/\\\\} + stash_value=${stash_value//\"/\\\"} + stash_value=${stash_value//\`/\\\`} + stash_value=${stash_value//\$/\\\$} + + if [[ ( -n "$(eval echo '$__varstash_alias__'$stash_name)" || + -n "$(eval echo '$__varstash_function__'$stash_name)" || + -n "$(eval echo '$__varstash_array__'$stash_name)" || + -n "$(eval echo '$__varstash_export__'$stash_name)" || + -n "$(eval echo '$__varstash_variable__'$stash_name)" || + -n "$(eval echo '$__varstash_nostash__'$stash_name)" ) + && -z $force ]]; then + + if [[ -z $already_stashed && ${already_stashed-_} == "_" ]]; then + local already_stashed=1 + else + already_stashed=1 + fi + + if [[ $stash_which == $stash_expression ]]; then + if [[ -z $run_from_smartcd ]]; then + echo "You have already stashed $stash_which, please specify \"-f\" if you want to overwrite another stashed value" + fi + + # Skip remaining work if we're not doing an assignment + shift + continue + fi + fi + + # Handle any alias that may exist under this name + if [[ -z $already_stashed ]]; then + local alias_def="$(eval alias $stash_which 2>/dev/null)" + if [[ -n $alias_def ]]; then + alias_def=${alias_def#alias } + eval "__varstash_alias__$stash_name=\"$alias_def\"" + local stashed=1 + fi + fi + if [[ $stash_which != $stash_expression && -n $_stashing_alias_assign ]]; then + eval "alias $stash_which=\"$stash_value\"" + fi + + # Handle any function that may exist under this name + if [[ -z $already_stashed ]]; then + local function_def="$(declare -f $stash_which)" + if [[ -n $function_def ]]; then + # make function definition quote-safe. because we are going to evaluate the + # source with "echo -e", we need to double-escape the backslashes (so 1 -> 4) + function_def=${function_def//\\/\\\\\\\\} + function_def=${function_def//\"/\\\"} + function_def=${function_def//\`/\\\`} + function_def=${function_def//\$/\\\$} + eval "__varstash_function__$stash_name=\"$function_def\"" + local stashed=1 + fi + fi + + # Handle any variable that may exist under this name + local vartype="$(declare -p $stash_which 2>/dev/null)" + if [[ -n $vartype ]]; then + if [[ -n $ZSH_VERSION ]]; then + local pattern="^typeset" + else + local pattern="^declare" + fi + if [[ $vartype =~ $pattern" -a" ]]; then + # varible is an array + if [[ -z $already_stashed ]]; then + eval "__varstash_array__$stash_name=(\"\${$stash_which""[@]}\")" + fi + + elif [[ $vartype =~ $pattern" -x" ]]; then + # variable is exported + if [[ -z $already_stashed ]]; then + eval "__varstash_export__$stash_name=\"\$$stash_which\"" + fi + if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then + eval "export $stash_which=\"$stash_value\"" + fi + else + # regular variable + if [[ -z $already_stashed ]]; then + eval "__varstash_variable__$stash_name=\"\$$stash_which\"" + fi + if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then + eval "$stash_which=\"$stash_value\"" + fi + + fi + local stashed=1 + fi + + if [[ -z $stashed ]]; then + # Nothing in the variable we're stashing, but make a note that we stashed so we + # do the right thing when unstashing. Without this, we take no action on unstash + + # Zsh bug sometimes caues + # (eval):1: command not found: __varstash_nostash___tmp__home_dolszewski_src_smartcd_RANDOM_VARIABLE=1 + # fixed in zsh commit 724fd07a67f, version 4.3.14 + if [[ -z $already_stashed ]]; then + eval "__varstash_nostash__$stash_name=1" + fi + + # In the case of a previously unset variable that we're assigning too, export it + if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then + eval "export $stash_which=\"$stash_value\"" + fi + fi + + shift + unset -v _stashing_alias_assign + done +} + +function autostash() { + local run_from_autostash=1 + while [[ -n $1 ]]; do + if [[ $1 == "alias" && $2 =~ "=" ]]; then + shift + local _stashing_alias_assign=1 + fi + + local already_stashed= + stash "$1" + if [[ -z $already_stashed ]]; then + local autostash_name=$(_mangle_var AUTOSTASH) + local varname=${1%%'='*} + apush $autostash_name "$varname" + fi + shift + unset -v _stashing_alias_assign + done +} + +function unstash() { + while [[ -n $1 ]]; do + local unstash_which=$1 + if [[ -z $unstash_which ]]; then + continue + fi + + local unstash_name=$(_mangle_var $unstash_which) + + # This bit is a little tricky. Here are the rules: + # 1) unstash any alias, function, or variable which matches + # 2) if one or more matches, but not all, delete any that did not + # 3) if none match but nostash is found, delete all + # 4) if none match and nostash not found, do nothing + + # Unstash any alias + if [[ -n "$(eval echo \$__varstash_alias__$unstash_name)" ]]; then + eval "alias $(eval echo \$__varstash_alias__$unstash_name)" + unset __varstash_alias__$unstash_name + local unstashed=1 + local unstashed_alias=1 + fi + + # Unstash any function + if [[ -n "$(eval echo \$__varstash_function__$unstash_name)" ]]; then + eval "function $(eval echo -e \"\$__varstash_function__$unstash_name\")" + unset __varstash_function__$unstash_name + local unstashed=1 + local unstashed_function=1 + fi + + # Unstash any variable + if [[ -n "$(declare -p __varstash_array__$unstash_name 2>/dev/null)" ]]; then + eval "$unstash_which=(\"\${__varstash_array__$unstash_name""[@]}\")" + unset __varstash_array__$unstash_name + local unstashed=1 + local unstashed_variable=1 + elif [[ -n "$(declare -p __varstash_export__$unstash_name 2>/dev/null)" ]]; then + eval "export $unstash_which=\"\$__varstash_export__$unstash_name\"" + unset __varstash_export__$unstash_name + local unstashed=1 + local unstashed_variable=1 + elif [[ -n "$(declare -p __varstash_variable__$unstash_name 2>/dev/null)" ]]; then + # Unset variable first to reset export + unset -v $unstash_which + eval "$unstash_which=\"\$__varstash_variable__$unstash_name\"" + unset __varstash_variable__$unstash_name + local unstashed=1 + local unstashed_variable=1 + fi + + # Unset any values which did not exist at time of stash + local nostash="$(eval echo \$__varstash_nostash__$unstash_name)" + unset __varstash_nostash__$unstash_name + if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_alias" ) ]]; then + unalias $unstash_which 2>/dev/null + fi + if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_function" ) ]]; then + unset -f $unstash_which 2>/dev/null + fi + if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_variable" ) ]]; then + # Don't try to unset illegal variable names + if ! [[ $unstash_which =~ [^a-zA-Z0-9_] || $unstash_which =~ ^[0-9] ]]; then + unset -v $unstash_which + fi + fi + + shift + done +} + +function autounstash() { + # If there is anything in (mangled) variable AUTOSTASH, then unstash it + local autounstash_name=$(_mangle_var AUTOSTASH) + if (( $(alen $autounstash_name) > 0 )); then + local run_from_autounstash=1 + while (( $(alen $autounstash_name) > 0 )); do + local autounstash_var=$(afirst $autounstash_name) + ashift $autounstash_name >/dev/null + unstash $autounstash_var + done + unset $autounstash_name + fi +} + +function _mangle_var() { + local mangle_var_where="${varstash_dir:-$PWD}" + mangle_var_where=${mangle_var_where//[^A-Za-z0-9]/_} + local mangled_name=${1//[^A-Za-z0-9]/_} + echo "_tmp_${mangle_var_where}_${mangled_name}" +} + +# vim: filetype=sh autoindent expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/.zshenv b/tests/.zshenv index 1c0a9d2..2311e2a 100644 --- a/tests/.zshenv +++ b/tests/.zshenv @@ -1,4 +1,6 @@ test -f "$TESTDIR/.zcompdump" && rm "$TESTDIR/.zcompdump" +AUTOENV_DEBUG=0 + source "$TESTDIR/../autoenv.plugin.zsh" -export ENV_AUTHORIZATION_FILE="$PWD/.env_auth" +export AUTOENV_ENV_FILENAME="$PWD/.env_auth" diff --git a/tests/_autoenv_stack.t b/tests/_autoenv_stack.t new file mode 100644 index 0000000..6f659c6 --- /dev/null +++ b/tests/_autoenv_stack.t @@ -0,0 +1,58 @@ +Tests for internal stack handling. + + $ source $TESTDIR/setup.sh + +Non-existing entries are allowed and handled without error. + + $ _autoenv_stack_entered_add non-existing + $ echo $_autoenv_stack_entered + non-existing + +Add existing entries. + + $ mkdir -p sub/sub2 + $ touch -t 201401010101 sub/file + $ _autoenv_stack_entered_add sub + $ _autoenv_stack_entered_add sub/file + $ _autoenv_stack_entered_add sub/sub2 + $ echo $_autoenv_stack_entered + non-existing sub sub/file sub/sub2 + + $ _autoenv_stack_entered_add non-existing + $ echo $_autoenv_stack_entered + sub sub/file sub/sub2 non-existing + + $ echo ${(k)_autoenv_stack_entered} + sub sub/file sub/sub2 non-existing + + $ echo $_autoenv_stack_entered_mtime + 0 1388538060 0 0 (glob) + +Touch the file and re-add it. + + $ touch -t 201401012359 sub/file + $ _autoenv_stack_entered_add sub/file + +The mtime should have been updated. + + $ echo ${_autoenv_stack_entered_mtime[sub/file]} + 1388620740 + +It should have moved to the end of the stack. + + $ echo ${(k)_autoenv_stack_entered} + sub sub/sub2 non-existing sub/file + +Test lookup of containing elements. + + $ _autoenv_stack_entered_contains sub/file + $ _autoenv_stack_entered_contains non-existing + $ _autoenv_stack_entered_contains not-added + [1] + +Test removing. + + $ _autoenv_stack_entered_remove sub + $ echo ${_autoenv_stack_entered} + sub/sub2 non-existing sub/file + diff --git a/tests/_autoenv_utils.t b/tests/_autoenv_utils.t new file mode 100644 index 0000000..79c4bf1 --- /dev/null +++ b/tests/_autoenv_utils.t @@ -0,0 +1,15 @@ +Tests for internal util methods. + + $ source $TESTDIR/setup.sh + +Non-existing entries are allowed and handled without error. + + $ mkdir -p sub/sub2 + $ touch file sub/file sub/sub2/file + +Should not get the file from the current dir. + $ _autoenv_get_file_upwards . file + + $ cd sub/sub2 + $ _autoenv_get_file_upwards . file + */_autoenv_utils.t/sub/file (glob) diff --git a/tests/autoenv.t b/tests/autoenv.t index f3ede0c..19d0592 100644 --- a/tests/autoenv.t +++ b/tests/autoenv.t @@ -1,6 +1,4 @@ -Ensure we have our mocked out ENV_AUTHORIZATION_FILE - - $ [[ $ENV_AUTHORIZATION_FILE[0,4] == '/tmp' ]] || return 1 + $ source $TESTDIR/setup.sh Lets set a simple .env action @@ -8,17 +6,18 @@ Lets set a simple .env action Manually create auth file - $ echo "$PWD/.env:$(echo echo ENTERED | shasum)" > $ENV_AUTHORIZATION_FILE + $ test_autoenv_add_to_env $PWD/.env $ cd . ENTERED Now try to make it accept it - $ unset _dotenv_stack_entered - $ rm $ENV_AUTHORIZATION_FILE - $ _dotenv_read_answer() { echo 'y' } + $ unset _autoenv_stack_entered + $ rm $AUTOENV_ENV_FILENAME + $ _autoenv_ask_for_yes() { echo "yes" } $ cd . - Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/autoenv.t/.env (glob) ********************************************** @@ -26,26 +25,24 @@ Now try to make it accept it ********************************************** - Would you like to authorize it? [y/N] + Would you like to authorize it? (type 'yes') yes ENTERED +The last "ENTERED" is because it executed the command. +Now lets see that it actually checks the shasum value. - -The last "ENTERED" is because it executed the command - -Now lets see that it actually checks the shasum value - - $ unset _dotenv_stack_entered + $ unset _autoenv_stack_entered $ cd . ENTERED - $ unset _dotenv_stack_entered - $ rm $ENV_AUTHORIZATION_FILE - $ echo "$PWD/.env:$(echo mischief | shasum)" > $ENV_AUTHORIZATION_FILE + $ unset _autoenv_stack_entered + $ rm $AUTOENV_ENV_FILENAME + $ test_autoenv_add_to_env $PWD/.env mischief $ cd . - Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/autoenv.t/.env (glob) ********************************************** @@ -53,20 +50,18 @@ Now lets see that it actually checks the shasum value ********************************************** - Would you like to authorize it? [y/N] + Would you like to authorize it? (type 'yes') yes ENTERED - - - Now, will it take no for an answer? - $ unset _dotenv_stack_entered - $ rm $ENV_AUTHORIZATION_FILE - $ _dotenv_read_answer() { echo 'n' } + $ unset _autoenv_stack_entered + $ rm $AUTOENV_ENV_FILENAME + $ _autoenv_ask_for_yes() { echo "no"; return 1 } $ cd . - Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/autoenv.t/.env (glob) ********************************************** @@ -74,16 +69,14 @@ Now, will it take no for an answer? ********************************************** - Would you like to authorize it? [y/N] + Would you like to authorize it? (type 'yes') no - - - -Lets also try one more time to ensure it didnt add it +Lets also try one more time to ensure it didn't add it. $ cd . - Attempting to load unauthorized env file: /tmp/cramtests-??????/autoenv.t/.env (glob) + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/autoenv.t/.env (glob) ********************************************** @@ -91,4 +84,4 @@ Lets also try one more time to ensure it didnt add it ********************************************** - Would you like to authorize it? [y/N] + Would you like to authorize it? (type 'yes') no diff --git a/tests/cwd.t b/tests/cwd.t index 9764e74..ea77aa0 100644 --- a/tests/cwd.t +++ b/tests/cwd.t @@ -1,29 +1,26 @@ -Test $PWD and $_dotenv_cwd. +Test $PWD, $_autoenv_from_dir and _autoenv_to_dir. -Ensure we have our mocked out ENV_AUTHORIZATION_FILE. - - $ [[ $ENV_AUTHORIZATION_FILE[0,4] == '/tmp' ]] || return 1 + $ source $TESTDIR/setup.sh Setup env actions / output. - $ DOTENV_LOOK_UPWARDS=1 + $ AUTOENV_LOOK_UPWARDS=1 $ mkdir -p sub/sub2 $ cd sub - $ echo 'echo ENTERED: cwd:${PWD:t} ${_dotenv_cwd:t}' >> .env - $ echo 'echo LEFT: cwd:${PWD:t} ${_dotenv_cwd:t}' >> .env.leave + $ echo 'echo ENTERED: PWD:${PWD:t} from:${_autoenv_from_dir:t} to:${_autoenv_to_dir:t}' > .env + $ echo 'echo LEFT: PWD:${PWD:t} from:${_autoenv_from_dir:t} to:${_autoenv_to_dir:t}' > .env.leave Manually create auth files. - $ echo "$PWD/$DOTENV_FILE_ENTER:$(echo $(<$DOTENV_FILE_ENTER) | shasum)" > $ENV_AUTHORIZATION_FILE - $ echo "$PWD/$DOTENV_FILE_LEAVE:$(echo $(<$DOTENV_FILE_LEAVE) | shasum)" >> $ENV_AUTHORIZATION_FILE + $ test_autoenv_auth_env_files The actual tests. $ cd . - ENTERED: cwd:sub sub + ENTERED: PWD:sub from:sub to:sub $ cd .. - LEFT: cwd:sub cwd.t + LEFT: PWD:sub from:sub to:cwd.t $ cd sub/sub2 - ENTERED: cwd:sub sub2 + ENTERED: PWD:sub from:cwd.t to:sub2 diff --git a/tests/leave.t b/tests/leave.t index 770b310..2e4e360 100644 --- a/tests/leave.t +++ b/tests/leave.t @@ -1,7 +1,4 @@ -Ensure we have our mocked out ENV_AUTHORIZATION_FILE - - $ [[ $ENV_AUTHORIZATION_FILE[0,4] == '/tmp' ]] || return 1 - + $ source $TESTDIR/setup.sh Lets set a simple .env action @@ -12,9 +9,10 @@ Lets set a simple .env action Change to the directory. - $ _dotenv_read_answer() { echo 'y' } + $ _autoenv_ask_for_yes() { echo "yes"; return 0 } $ cd . - Attempting to load unauthorized env file: /tmp/cramtests-??????/leave.t/sub/.env (glob) + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/leave.t/sub/.env (glob) ********************************************** @@ -22,15 +20,16 @@ Change to the directory. ********************************************** - Would you like to authorize it? [y/N] + Would you like to authorize it? (type 'yes') yes ENTERED Leave the directory and answer "no". - $ _dotenv_read_answer() { echo 'n' } + $ _autoenv_ask_for_yes() { echo "no"; return 1 } $ cd .. - Attempting to load unauthorized env file: /tmp/cramtests-??????/leave.t/sub/.env.leave (glob) + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/leave.t/sub/.env.leave (glob) ********************************************** @@ -38,14 +37,15 @@ Leave the directory and answer "no". ********************************************** - Would you like to authorize it? [y/N] + Would you like to authorize it? (type 'yes') no $ cd sub ENTERED - $ _dotenv_read_answer() { echo 'y' } + $ _autoenv_ask_for_yes() { echo "yes"; return 0 } $ cd .. - Attempting to load unauthorized env file: /tmp/cramtests-??????/leave.t/sub/.env.leave (glob) + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/leave.t/sub/.env.leave (glob) ********************************************** @@ -53,13 +53,13 @@ Leave the directory and answer "no". ********************************************** - Would you like to authorize it? [y/N] + Would you like to authorize it? (type 'yes') yes LEFT Now check with subdirs, looking upwards. - $ DOTENV_LOOK_UPWARDS=1 + $ AUTOENV_LOOK_UPWARDS=1 $ mkdir sub/child $ cd sub/child ENTERED @@ -71,7 +71,7 @@ Now check with subdirs, looking upwards. Now check with subdirs, not looking at parent dirs. - $ DOTENV_LOOK_UPWARDS=0 + $ AUTOENV_LOOK_UPWARDS=0 $ cd sub/child $ cd .. ENTERED @@ -80,10 +80,10 @@ Now check with subdirs, not looking at parent dirs. LEFT -Test that .env is sourced only once with DOTENV_HANDLE_LEAVE=0. +Test that .env is sourced only once with AUTOENV_HANDLE_LEAVE=0. - $ unset _dotenv_stack_entered - $ DOTENV_HANDLE_LEAVE=0 + $ unset _autoenv_stack_entered + $ AUTOENV_HANDLE_LEAVE=0 $ cd sub ENTERED $ cd .. diff --git a/tests/recurse-upwards.t b/tests/recurse-upwards.t new file mode 100644 index 0000000..f4c2780 --- /dev/null +++ b/tests/recurse-upwards.t @@ -0,0 +1,167 @@ +Test recursing into parent .env files. + + $ source $TESTDIR/setup.sh + +Setup env actions / output. + + $ AUTOENV_LOOK_UPWARDS=1 + +Create env files in root dir. + + $ echo 'echo ENTERED_root: PWD:${PWD:t} from:${_autoenv_from_dir:t} to:${_autoenv_to_dir:t}' > .env + $ echo 'echo LEFT_root: PWD:${PWD:t} from:${_autoenv_from_dir:t} to:${_autoenv_to_dir:t}' > .env.leave + $ test_autoenv_auth_env_files + +Create env files in sub dir. + + $ mkdir -p sub/sub2 + $ cd sub + ENTERED_root: PWD:recurse-upwards.t from:recurse-upwards.t to:sub + + $ echo 'echo ENTERED_sub: PWD:${PWD:t} from:${_autoenv_from_dir:t} to:${_autoenv_to_dir:t}' > .env + $ echo 'echo LEFT_sub: PWD:${PWD:t} from:${_autoenv_from_dir:t} to:${_autoenv_to_dir:t}' > .env.leave + $ test_autoenv_auth_env_files + +The actual tests. + + $ cd . + ENTERED_sub: PWD:sub from:sub to:sub + + $ cd .. + LEFT_sub: PWD:sub from:sub to:recurse-upwards.t + + $ cd sub/sub2 + ENTERED_sub: PWD:sub from:recurse-upwards.t to:sub2 + + $ cd .. + +Changing the .env file should re-source it. + + $ echo 'echo ENTER2' >> .env + +Set timestamp of auth file into the past, so it gets seen as new below. + + $ touch -t 201401010101 .env + + $ test_autoenv_auth_env_files + $ cd . + ENTERED_sub: PWD:sub from:sub to:sub + ENTER2 + +Add sub/sub2/.env file, with a call to autoenv_source_parent. + + $ echo "echo autoenv_source_parent_from_sub2:\nautoenv_source_parent\necho done_sub2\n" > sub2/.env + $ test_autoenv_add_to_env sub2/.env + $ cd sub2 + autoenv_source_parent_from_sub2: + ENTERED_sub: PWD:sub from:sub to:sub2 + ENTER2 + done_sub2 + +Move sub/.env away, now the root .env file should get sourced. + + $ mv ../.env ../.env.out + $ touch -t 201401010102 .env + $ cd . + autoenv_source_parent_from_sub2: + ENTERED_root: PWD:recurse-upwards.t from:sub2 to:sub2 + done_sub2 + $ mv ../.env.out ../.env + +Prepend call to autoenv_source_parent to sub/.env file. + + $ cd .. + $ sed -i -e "1s/^/echo autoenv_source_parent_from_sub:\nautoenv_source_parent\n/" .env + $ echo "echo done_sub" >> .env + $ touch -t 201401010103 .env + $ test_autoenv_auth_env_files + + $ cd . + autoenv_source_parent_from_sub: + ENTERED_root: PWD:recurse-upwards.t from:sub to:sub + ENTERED_sub: PWD:sub from:sub to:sub + ENTER2 + done_sub + + +Add sub/sub2/.env file. + + $ echo -e "echo autoenv_source_parent_from_sub2:\nautoenv_source_parent\necho done_sub2\n" > sub2/.env + $ test_autoenv_add_to_env sub2/.env + $ cd sub2 + autoenv_source_parent_from_sub2: + autoenv_source_parent_from_sub: + ENTERED_root: PWD:recurse-upwards.t from:sub to:sub + ENTERED_sub: PWD:sub from:sub to:sub + ENTER2 + done_sub + done_sub2 + +Go to root. +This should not trigger the enter event, because it was handled via +autoenv_source_parent already. + + $ cd ../.. + LEFT_sub: PWD:sub from:sub2 to:recurse-upwards.t + + +Changing the root .env should trigger re-authentication via autoenv_source_parent. + +First, let's answer "no". + + $ echo "echo NEW" > .env + $ _autoenv_ask_for_yes() { echo "no"; return 1 } + $ cd sub + autoenv_source_parent_from_sub: + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/recurse-upwards.t/.env (glob) + + ********************************************** + + echo NEW + + ********************************************** + + Would you like to authorize it? (type 'yes') no + ENTERED_sub: PWD:sub from:recurse-upwards.t to:sub + ENTER2 + done_sub + +Now with "yes". +This currently does not trigger re-execution of the .env file. + + $ _autoenv_ask_for_yes() { echo "yes"; return 0 } + $ cd . + +Touching the .env file will now source the parent env file. + + $ touch -t 201401010104 .env + $ cd . + autoenv_source_parent_from_sub: + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/recurse-upwards.t/.env (glob) + + ********************************************** + + echo NEW + + ********************************************** + + Would you like to authorize it? (type 'yes') yes + NEW + ENTERED_sub: PWD:sub from:sub to:sub + ENTER2 + done_sub + + + $ cd .. + LEFT_sub: PWD:sub from:sub to:recurse-upwards.t + $ mkdir sub/sub2/sub3 + $ cd sub/sub2/sub3 + autoenv_source_parent_from_sub2: + autoenv_source_parent_from_sub: + NEW + ENTERED_sub: PWD:sub from:recurse-upwards.t to:sub + ENTER2 + done_sub + done_sub2 diff --git a/tests/setup.sh b/tests/setup.sh new file mode 100644 index 0000000..90bfc51 --- /dev/null +++ b/tests/setup.sh @@ -0,0 +1,18 @@ +# Ensure we have our mocked out AUTOENV_ENV_FILENAME +# (via .zshenv). + +[[ $AUTOENV_ENV_FILENAME[0,4] == '/tmp' ]] || return 1 + +# Reset any authentication. +echo -n > $AUTOENV_ENV_FILENAME + +# Add file $1 (with optional hash $2) to authentication file. +test_autoenv_add_to_env() { + _autoenv_hash_pair $1 $2 >> $AUTOENV_ENV_FILENAME +} + +# Add enter and leave env files to authentication file. +test_autoenv_auth_env_files() { + test_autoenv_add_to_env $PWD/$AUTOENV_FILE_ENTER + test_autoenv_add_to_env $PWD/$AUTOENV_FILE_LEAVE +} diff --git a/tests/varstash.t b/tests/varstash.t new file mode 100644 index 0000000..7458044 --- /dev/null +++ b/tests/varstash.t @@ -0,0 +1,32 @@ +Test varstash integration. + + $ source $TESTDIR/setup.sh + +Setup test environment. + + $ mkdir sub + $ cd sub + $ echo 'echo ENTER; autostash FOO=baz' > $AUTOENV_FILE_ENTER + $ echo 'echo LEAVE; autounstash' > $AUTOENV_FILE_LEAVE + +Manually create auth file + + $ test_autoenv_auth_env_files + +Set environment variable. + + $ FOO=bar + +Activating the env stashes it and applies a new value. + + $ cd . + ENTER + $ echo $FOO + baz + +Leaving the directory unstashes it. + + $ cd .. + LEAVE + $ echo $FOO + bar