diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1700a56 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tests/*.err diff --git a/.travis.yml b/.travis.yml index b56caf2..715854b 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_full" diff --git a/Makefile b/Makefile index bed40b1..9859775 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,34 @@ -.PHONY: itests tests +# Default, can be overridden using "make test ZDOTDIR=...". +ZDOTDIR:=${CURDIR}/tests/ZDOTDIR -itests: - ZDOTDIR="${PWD}/tests" cram -i --shell=zsh tests +# Export it, and make it absolute. +override export ZDOTDIR:=$(abspath $(ZDOTDIR)) -tests: - ZDOTDIR="${PWD}/tests" cram --shell=zsh tests +test: + cram --shell=zsh -v tests + +itest: + cram -i --shell=zsh tests + +# Run tests with all ZDOTDIRs. +test_full: + for i in $(wildcard tests/ZDOTDIR*); do \ + echo "ZDOTDIR=$$i"; \ + ZDOTDIR=${CURDIR}/$$i cram --shell=zsh -v tests; \ + echo; \ + done + +# 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): + cram --shell=zsh -v $@ +.PHONY: $(_TESTS_REL_AND_ABS) + +.PHONY: itest test + +clean: + $(RM) tests/*.err diff --git a/README.md b/README.md index 34ac633..0d6436c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/Tarrasch/zsh-autoenv/trend.png)](https://bitdeli.com/free "Bitdeli Badge") -[![Build Status](https://travis-ci.org/Tarrasch/zsh-autoenv.png)](https://travis-ci.org/Tarrasch/zsh-autoenv) +[![Build Status](https://travis-ci.org/Tarrasch/zsh-autoenv.svg?branch=master)](https://travis-ci.org/Tarrasch/zsh-autoenv) # Autoenv for zsh @@ -26,6 +26,13 @@ Add zgen load Tarrasch/zsh-autoenv to your `.zshrc` where you're loading your other plugins. +### Manually + +Clone the repository and source it from your `~/.zshrc` file: + + git clone https://github.com/Tarrasch/zsh-autoenv ~/.dotfiles/lib/zsh-autoenv + echo 'source ~/.dotfiles/lib/zsh-autoenv/autoenv.zsh' >> ~/.zshrc +>>>>>>> upstream/master ## Credits diff --git a/autoenv.zsh b/autoenv.zsh index ff8b4a7..3ec01e9 100644 --- a/autoenv.zsh +++ b/autoenv.zsh @@ -1,70 +1,350 @@ -# Stolen from +# Initially based on # https://github.com/joshuaclayton/dotfiles/blob/master/zsh_profile.d/autoenv.zsh -export ENV_AUTHORIZATION_FILE=$HOME/.env_auth +export AUTOENV_ENV_FILENAME=$HOME/.env_auth +[ -e $AUTOENV_ENV_FILENAME ] || touch $AUTOENV_ENV_FILENAME -_dotenv_hash_pair() { - env_file=$1 - env_shasum=$(shasum $env_file | cut -d' ' -f1) - echo "$env_file:$env_shasum" -} +# Name of file to look for when entering directories. +: ${AUTOENV_FILE_ENTER:=.env} -_dotenv_authorized_env_file() { - env_file=$1 - pair=$(_dotenv_hash_pair $env_file) - touch $ENV_AUTHORIZATION_FILE - \grep -Gq $pair $ENV_AUTHORIZATION_FILE -} +# Name of file to look for when leaving directories. +# Requires AUTOENV_HANDLE_LEAVE=1. +: ${AUTOENV_FILE_LEAVE:=.env.leave} -_dotenv_authorize() { - env_file=$1 - _dotenv_deauthorize $env_file - _dotenv_hash_pair $env_file >> $ENV_AUTHORIZATION_FILE -} +# Look for .env in parent dirs? +: ${AUTOENV_LOOK_UPWARDS:=1} -_dotenv_deauthorize() { - env_file=$1 - echo $(grep -Gv $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE -} +# Handle leave events when changing away from a subtree, where an "enter" +# event was handled? +: ${AUTOENV_HANDLE_LEAVE:=1} -_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)" -} +# Enable debugging. Multiple levels are supported (max 2). +: ${AUTOENV_DEBUG:=0} -# This function can be mocked in tests -_dotenv_read_answer() { - read answer -} +# (Temporarily) disable zsh-autoenv. This gets looked at in the chpwd handler. +: ${AUTOENV_DISABLED:=0} -_dotenv_source_env() { - local env_file="$PWD/.env" +# 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 ${autoenv_env_file:h}) - if [[ -f $env_file ]] - then - if _dotenv_authorized_env_file $env_file - then - source $env_file - return 0 - fi - - _dotenv_print_unauthorized_message $env_file - - _dotenv_read_answer - - if [[ $answer == 'y' ]] - then - _dotenv_authorize $env_file - source $env_file - fi + 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" + _autoenv_source $parent_env_file enter fi } -chpwd_functions=($chpwd_functions _dotenv_source_env) +# Internal functions. {{{ +# Internal: stack of entered (and handled) directories. {{{ +typeset -g -a _autoenv_stack_entered +_autoenv_stack_entered=() +# -g: make it global, this is required when used with antigen. +typeset -g -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 + + # Remove any existing entry. + _autoenv_stack_entered_remove $env_file + + _autoenv_debug "[stack] adding: $env_file" 2 + + # 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) +} + + +# zstat_mime helper, conditionally defined. +# Load zstat module, but only its builtin `zstat`. +if ! zmodload -F zsh/stat b:zstat 2>/dev/null; then + # If the module is not available, define a wrapper around `stat`, and use its + # terse output instead of format, which is not supported by busybox. + # Assume '+mtime' as $1. + _autoenv_get_file_mtime() { + setopt localoptions pipefail + stat -t $1 2>/dev/null | cut -f13 -d ' ' || echo 0 + } +else + _autoenv_get_file_mtime() { + zstat +mtime $1 2>/dev/null || echo 0 + } +fi + + +# Remove an entry from the stack. +_autoenv_stack_entered_remove() { + local env_file=$1 + _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? +# This checks for the env_file ($1) as-is and with symlinks resolved. +_autoenv_stack_entered_contains() { + local env_file=$1 + local f i + if (( ${+_autoenv_stack_entered[(r)${env_file}]} )); then + # Entry is in stack. + f=$env_file + else + for i in $_autoenv_stack_entered; do + if [[ ${i:A} == ${env_file:A} ]]; then + # Entry is in stack (compared with resolved symlinks). + f=$i + break + fi + done + fi + if [[ -n $f ]]; then + if [[ $_autoenv_stack_entered_mtime[$f] == $(_autoenv_get_file_mtime $f) ]]; 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 $color ]]; 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 +} +# }}} + + +# Generate hash pair for a given file ($1). +# A fixed hash value can be given as 2nd arg, but is used with tests only. +_autoenv_hash_pair() { + local env_file=${1:A} + local env_shasum=${2:-} + if [[ -z $env_shasum ]]; then + if ! [[ -e $env_file ]]; then + echo "Missing file argument for _autoenv_hash_pair!" >&2 + return 1 + fi + 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:A} + _autoenv_deauthorize $env_file + _autoenv_hash_pair $env_file >>| $AUTOENV_ENV_FILENAME +} + +# Deauthorize a given filename, by removing it from the auth file. +# This uses `test -s` to only handle non-empty files, and a subshell to +# allow for writing to the same file again. +_autoenv_deauthorize() { + local env_file=${1:A} + if [[ -s $AUTOENV_ENV_FILENAME ]]; then + echo "$(\grep -vF :${env_file}: $AUTOENV_ENV_FILENAME)" >| $AUTOENV_ENV_FILENAME + fi +} + +# This function can be mocked in tests +_autoenv_ask_for_yes() { + local answer + read answer + if [[ $answer == "yes" ]]; then + return 0 + else + return 1 + fi +} + +# Args: 1: absolute path to env file (resolved symlinks). +_autoenv_check_authorized_env_file() { + if ! [[ -f $1 ]]; then + return 1 + fi + if ! _autoenv_authorized_env_file $1; then + echo "Attempting to load unauthorized env file!" + command ls -l $1 + echo "" + echo "**********************************************" + echo "" + cat $1 + echo "" + echo "**********************************************" + echo "" + echo -n "Would you like to authorize it? (type 'yes') " + + if ! _autoenv_ask_for_yes; then + return 1 + fi + + _autoenv_authorize $1 + fi + return 0 +} + +# Get directory of this file (absolute, with resolved symlinks). +_autoenv_source_dir=${0:A:h} + +_autoenv_source() { + local env_file=$1 + autoenv_event=$2 + local _autoenv_envfile_dir=${3:-${1:A:h}} + + autoenv_from_dir=$_autoenv_chpwd_prev_dir + autoenv_to_dir=$PWD + autoenv_env_file=$env_file + + # 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 + _autoenv_debug "== SOURCE: ${bold_color:-}$env_file${reset_color:-}\n PWD: $PWD" + (( _autoenv_debug_indent++ )) + source $env_file + (( _autoenv_debug_indent-- )) + _autoenv_debug "== END SOURCE ==" + + if [[ $autoenv_event == enter ]]; then + _autoenv_stack_entered_add $env_file + fi + + # 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 autoenv_env_file +} + +_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() { + _autoenv_debug "Calling chpwd handler: PWD=$PWD" + + if (( $AUTOENV_DISABLED )); then + _autoenv_debug "Disabled (AUTOENV_DISABLED)." + return + fi + + local env_file="$PWD/$AUTOENV_FILE_ENTER" + _autoenv_debug "env_file: $env_file" + + # Handle leave event for previously sourced env files. + 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:h} + if ! [[ ${PWD}/ == ${prev_dir}/* ]]; then + 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 + + # Unstash any autostashed stuff. + varstash_dir=$prev_dir autounstash + + _autoenv_stack_entered_remove $prev_file + fi + done + fi + + 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 + + # 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 + + if ! _autoenv_check_authorized_env_file $env_file; then + _autoenv_chpwd_prev_dir=$PWD + return + fi + + # 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 _autoenv_chpwd_handler + +# Look in current directory already. +_autoenv_chpwd_handler diff --git a/init.zsh b/init.zsh new file mode 100644 index 0000000..b6aca49 --- /dev/null +++ b/init.zsh @@ -0,0 +1 @@ +. $(dirname $0)/autoenv.zsh diff --git a/lib/varstash b/lib/varstash new file mode 100644 index 0000000..17bcd62 --- /dev/null +++ b/lib/varstash @@ -0,0 +1,344 @@ +################################################################################ +# 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 + # Using substitution to avoid using regex, which might fail to load on Zsh (minimal system). + if [[ ${unstash_which//[^a-zA-Z0-9_]/} == $unstash_which && $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/ZDOTDIR.clobber/.zshenv b/tests/ZDOTDIR.clobber/.zshenv new file mode 100644 index 0000000..62037dc --- /dev/null +++ b/tests/ZDOTDIR.clobber/.zshenv @@ -0,0 +1,3 @@ +source ${ZDOTDIR}/../ZDOTDIR/.zshenv + +setopt noclobber diff --git a/tests/ZDOTDIR.invalid-module_path/.zshenv b/tests/ZDOTDIR.invalid-module_path/.zshenv new file mode 100644 index 0000000..baa1de6 --- /dev/null +++ b/tests/ZDOTDIR.invalid-module_path/.zshenv @@ -0,0 +1,13 @@ +# Use invalid module path for zsh, to test alternative zstat implementation. + +# Pre-load zsh/parameter, where we do not have/need(?) an alternative +# implementation. +zmodload zsh/parameter + +module_path=(/dev/null) + +zstat() { + echo "Should not get called." +} + +source ${ZDOTDIR}/../ZDOTDIR/.zshenv diff --git a/tests/ZDOTDIR.loadviafunction/.zshenv b/tests/ZDOTDIR.loadviafunction/.zshenv new file mode 100644 index 0000000..4e41fbc --- /dev/null +++ b/tests/ZDOTDIR.loadviafunction/.zshenv @@ -0,0 +1,12 @@ +test -f "$TESTDIR/.zcompdump" && rm "$TESTDIR/.zcompdump" + +AUTOENV_DEBUG=0 + +antigen-like-loader-function() { + source "$TESTDIR/../autoenv.plugin.zsh" +} +antigen-like-loader-function + +export AUTOENV_ENV_FILENAME="$PWD/.env_auth" + +echo -n > $AUTOENV_ENV_FILENAME diff --git a/tests/.zshenv b/tests/ZDOTDIR/.zshenv similarity index 51% rename from tests/.zshenv rename to tests/ZDOTDIR/.zshenv index 1c0a9d2..029e73d 100644 --- a/tests/.zshenv +++ b/tests/ZDOTDIR/.zshenv @@ -1,4 +1,8 @@ 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" + +echo -n > $AUTOENV_ENV_FILENAME diff --git a/tests/_autoenv_stack.t b/tests/_autoenv_stack.t new file mode 100644 index 0000000..dd56fff --- /dev/null +++ b/tests/_autoenv_stack.t @@ -0,0 +1,60 @@ +Tests for internal stack handling. + + $ source $TESTDIR/setup.sh || return 1 + +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 + $ touch -t 201401010102 sub + $ touch -t 201401010103 sub/sub2 + $ _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 + 1388538180 1388538060 1388538120 0 + +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..303cd24 --- /dev/null +++ b/tests/_autoenv_utils.t @@ -0,0 +1,87 @@ +Tests for internal util methods. + + $ source $TESTDIR/setup.sh || return 1 + +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) + + +Tests for _autoenv_authorize. {{{ + +Auth file is empty. + + $ cd ../.. + $ cat $AUTOENV_ENV_FILENAME + +Failed authorization should keep the auth file empty. + + $ _autoenv_authorize does-not-exist + Missing file argument for _autoenv_hash_pair! + [1] + $ cat $AUTOENV_ENV_FILENAME + +Now adding some auth pair. + + $ echo first > first + $ _autoenv_authorize first + $ cat $AUTOENV_ENV_FILENAME + :/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob) + +And a second one. + + $ echo second > second + $ _autoenv_authorize second + $ cat $AUTOENV_ENV_FILENAME + :/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob) + :/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob) + +And a third. + + $ echo third > third + $ _autoenv_authorize third + $ cat $AUTOENV_ENV_FILENAME + :/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob) + :/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob) + :/tmp/cramtests-*/_autoenv_utils.t/third:ad180453bf8a374a15df3e90a78c180230146a7c:1 (glob) + +Re-add the second one, with the same hash. + + $ _autoenv_authorize second + $ cat $AUTOENV_ENV_FILENAME + :/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob) + :/tmp/cramtests-*/_autoenv_utils.t/third:ad180453bf8a374a15df3e90a78c180230146a7c:1 (glob) + :/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob) + +Re-add the first one, with a new hash. + + $ echo one more line >> first + $ _autoenv_authorize first + $ cat $AUTOENV_ENV_FILENAME + :/tmp/cramtests-*/_autoenv_utils.t/third:ad180453bf8a374a15df3e90a78c180230146a7c:1 (glob) + :/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob) + :/tmp/cramtests-*/_autoenv_utils.t/first:65eb010197b73ddc109b7210080f97a87f53451e:1 (glob) +}}} + + +Explicit calls to _autoenv_get_file_mtime to test alternative implementation +of _autoenv_get_file_mtime (via ZDOTDIR.invalid-module_path/). + + $ _autoenv_get_file_mtime non-existing + 0 + $ touch -t 201401010101 file + $ _autoenv_get_file_mtime file + 1388538060 + $ mkdir dir + $ touch -t 201401010102 dir + $ _autoenv_get_file_mtime dir + 1388538120 diff --git a/tests/autoenv.t b/tests/autoenv.t index c4f1f41..7618385 100644 --- a/tests/autoenv.t +++ b/tests/autoenv.t @@ -1,89 +1,87 @@ -Ensure we have our mocked out ENV_AUTHORIZATION_FILE - - $ [[ $ENV_AUTHORIZATION_FILE[0,4] == '/tmp' ]] || return 1 + $ source $TESTDIR/setup.sh || return 1 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 + $ test_autoenv_add_to_env $PWD/.env $ cd . - blah + ENTERED Now try to make it accept it - $ rm $ENV_AUTHORIZATION_FILE - $ _dotenv_read_answer() { answer='y' } + $ _autoenv_stack_entered=() + $ rm $AUTOENV_ENV_FILENAME + $ _autoenv_ask_for_yes() { echo "yes" } $ 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? (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 "blah" is because it executed the command - -Now lets see that it actually checks the shasum value - + $ _autoenv_stack_entered=() $ cd . - blah - $ rm $ENV_AUTHORIZATION_FILE - $ echo "$PWD/.env:$(echo mischief | shasum)" > $ENV_AUTHORIZATION_FILE + ENTERED + + $ _autoenv_stack_entered=() + $ rm $AUTOENV_ENV_FILENAME + $ test_autoenv_add_to_env $PWD/.env mischief $ 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? (type 'yes') yes + ENTERED Now, will it take no for an answer? - $ rm $ENV_AUTHORIZATION_FILE - $ _dotenv_read_answer() { answer='n' } + $ _autoenv_stack_entered=() + $ rm $AUTOENV_ENV_FILENAME + $ _autoenv_ask_for_yes() { echo "no"; return 1 } $ 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? (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: /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? (type 'yes') no diff --git a/tests/cwd.t b/tests/cwd.t new file mode 100644 index 0000000..9a0bd13 --- /dev/null +++ b/tests/cwd.t @@ -0,0 +1,40 @@ +Test $PWD, $autoenv_event, $autoenv_from_dir and $autoenv_to_dir. + + $ source $TESTDIR/setup.sh || return 1 + +Setup env actions / output. + + $ AUTOENV_LOOK_UPWARDS=1 + $ mkdir -p sub/sub2 + $ cd sub + $ echo 'echo ENTERED: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env + $ echo 'echo LEFT: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env.leave + +Manually create auth files. + + $ test_autoenv_auth_env_files + +The actual tests. + + $ cd . + ENTERED: PWD:sub pwd:sub from:sub to:sub event:enter + + $ cd .. + LEFT: PWD:cwd.t pwd:cwd.t from:sub to:cwd.t event:leave + + $ cd sub/sub2 + ENTERED: PWD:sub2 pwd:sub2 from:cwd.t to:sub2 event:enter + +Check that symlinked dirs get handled correctly. + + $ cd ../.. + LEFT: PWD:cwd.t pwd:cwd.t from:sub2 to:cwd.t event:leave + $ ln -s sub sub_linked + $ cd sub_linked + ENTERED: PWD:sub_linked pwd:sub_linked from:cwd.t to:sub_linked event:enter + $ cd sub2 + + $ cd ../.. + LEFT: PWD:cwd.t pwd:cwd.t from:sub2 to:cwd.t event:leave + $ cd sub_linked/sub2 + ENTERED: PWD:sub2 pwd:sub2 from:cwd.t to:sub2 event:enter diff --git a/tests/leave.t b/tests/leave.t new file mode 100644 index 0000000..ad70e2f --- /dev/null +++ b/tests/leave.t @@ -0,0 +1,115 @@ + $ source $TESTDIR/setup.sh || 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. + + $ _autoenv_ask_for_yes() { echo "yes"; return 0 } + $ cd . + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/leave.t/sub/.env (glob) + + ********************************************** + + echo ENTERED + + ********************************************** + + Would you like to authorize it? (type 'yes') yes + ENTERED + + +Leave the directory and answer "no". + + $ _autoenv_ask_for_yes() { echo "no"; return 1 } + $ cd .. + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/leave.t/sub/.env.leave (glob) + + ********************************************** + + echo LEFT + + ********************************************** + + Would you like to authorize it? (type 'yes') no + + + $ cd sub + ENTERED + $ _autoenv_ask_for_yes() { echo "yes"; return 0 } + $ cd .. + Attempting to load unauthorized env file! + -* /tmp/cramtests-*/leave.t/sub/.env.leave (glob) + + ********************************************** + + echo LEFT + + ********************************************** + + Would you like to authorize it? (type 'yes') yes + LEFT + + +Now check with subdirs, looking upwards. + + $ AUTOENV_LOOK_UPWARDS=1 + $ mkdir sub/child + $ cd sub/child + ENTERED + $ cd . + $ cd .. + $ cd .. + LEFT + + +Now check with subdirs, not looking at parent dirs. + + $ AUTOENV_LOOK_UPWARDS=0 + $ cd sub/child + $ cd .. + ENTERED + $ cd child + $ cd ../.. + LEFT + + +Test that .env is sourced only once with AUTOENV_HANDLE_LEAVE=0. + + $ unset _autoenv_stack_entered + $ AUTOENV_HANDLE_LEAVE=0 + $ cd sub + ENTERED + $ cd .. + $ cd sub + + +Test that "leave" is not triggered when entering an outside dir via symlink. + + $ AUTOENV_HANDLE_LEAVE=1 + $ cd .. + LEFT + $ mkdir outside + $ cd outside + $ echo 'echo ENTERED outside: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env + $ echo 'echo LEFT outside: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env.leave + $ test_autoenv_auth_env_files + + $ cd .. + $ ln -s ../outside sub/symlink + $ cd sub + ENTERED + $ cd symlink + ENTERED outside: PWD:symlink pwd:symlink from:sub to:symlink event:enter + + $ cd ../.. + LEFT + LEFT outside: PWD:leave.t pwd:leave.t from:symlink to:leave.t event:leave + $ cd sub/symlink + ENTERED outside: PWD:symlink pwd:symlink from:leave.t to:symlink event:enter diff --git a/tests/recurse-upwards.t b/tests/recurse-upwards.t new file mode 100644 index 0000000..20a35d1 --- /dev/null +++ b/tests/recurse-upwards.t @@ -0,0 +1,167 @@ +Test recursing into parent .env files. + + $ source $TESTDIR/setup.sh || return 1 + +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:sub 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:recurse-upwards.t from:sub to:recurse-upwards.t + + $ cd sub/sub2 + ENTERED_sub: PWD:sub2 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:sub2 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:sub2 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:sub 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:sub2 from:sub to:sub2 + ENTERED_sub: PWD:sub2 from:sub to:sub2 + 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:recurse-upwards.t 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:recurse-upwards.t 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:sub3 from:recurse-upwards.t to:sub3 + ENTER2 + done_sub + done_sub2 diff --git a/tests/setup.sh b/tests/setup.sh new file mode 100644 index 0000000..5dcb62b --- /dev/null +++ b/tests/setup.sh @@ -0,0 +1,29 @@ +# Setup for tests. +# +# It returns 1 in case of errors, and no tests should be run then! +# +# Ensure we have our mocked out AUTOENV_ENV_FILENAME +# (via .zshenv). + +# Treat unset variables as errors. +# Not handled in varstash yet. +# setopt nounset + +if [[ $AUTOENV_ENV_FILENAME[0,4] != '/tmp' ]]; then + echo "AUTOENV_ENV_FILENAME is not in /tmp. Aborting." + return 1 +fi + +# 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..85d665f --- /dev/null +++ b/tests/varstash.t @@ -0,0 +1,93 @@ +Test varstash integration. + + $ source $TESTDIR/setup.sh || return 1 + +Setup test environment. + + $ mkdir sub + $ cd sub + $ echo 'echo ENTER; autostash FOO=changed' > $AUTOENV_FILE_ENTER + $ echo 'echo LEAVE; autounstash' > $AUTOENV_FILE_LEAVE + +Manually create auth file + + $ test_autoenv_auth_env_files + +Set environment variable. + + $ FOO=orig + +Activating the env stashes it and applies a new value. + + $ cd . + ENTER + $ echo $FOO + changed + +Leaving the directory unstashes it. + + $ cd .. + LEAVE + $ echo $FOO + orig + + +Test autounstashing when leaving a directory. {{{ + +Setup: + + $ unset VAR + $ cd sub + ENTER + $ echo 'echo ENTER; autostash VAR=changed' >| $AUTOENV_FILE_ENTER + $ echo 'echo LEAVE; echo "no explicit call to autounstash"' >| $AUTOENV_FILE_LEAVE + $ test_autoenv_auth_env_files + +$VAR is unset: + + $ echo VAR_set:$+VAR + VAR_set:0 + +Trigger the autostashing in the enter file. + + $ cd .. + LEAVE + no explicit call to autounstash + $ cd sub + ENTER + $ echo $VAR + changed + +Now leave again. + + $ cd .. + LEAVE + no explicit call to autounstash + $ echo VAR_set:$+VAR + VAR_set:0 + +Remove the leave file, auto-unstashing should still happen. + + $ rm sub/$AUTOENV_FILE_LEAVE + $ cd sub + ENTER + $ echo $VAR + changed + $ cd .. + $ echo VAR_set:$+VAR + VAR_set:0 + +And once again where a value gets restored. + + $ VAR=orig_2 + $ echo $VAR + orig_2 + $ cd sub + ENTER + $ echo $VAR + changed + $ cd .. + $ echo $VAR + orig_2 + +}}}