zsh-autoenv/lib/varstash

313 lines
12 KiB
Bash

################################################################################
# Stash/unstash support for per-directory variables
#
# Adopted for zsh-autoenv.
#
# Copyright (c) 2009,2012 Dave Olszewski <cxreg@pobox.com>
# 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 in one line, resulting in 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.
#
################################################################################
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 [[ $vartype == 'typeset -a'* ]]; then
# varible is an array
if [[ -z $already_stashed ]]; then
eval "__varstash_array__$stash_name=(\"\${$stash_which""[@]}\")"
fi
elif [[ $vartype == "export "* || $vartype == 'typeset -x'* ]]; then
# variable is exported
if [[ -z $already_stashed ]]; then
eval "export __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 "export __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 get_autostash_array_name() {
local autostash_name=$(_mangle_var AUTOSTASH)
# Create a scalar variable linked to an array (for exporting).
local autostash_array_name=${(L)autostash_name}
if ! (( ${(P)+autostash_array_name} )); then
# Conditionally set it, to prevent error with Zsh 4.3:
# can't tie already tied scalar: ...
typeset -xT $autostash_name $autostash_array_name
fi
ret=$autostash_array_name
}
function autostash() {
local run_from_autostash=1
local ret varname
local already_stashed=
while [[ -n $1 ]]; do
if [[ $1 == "alias" && $2 == *=* ]]; then
shift
local _stashing_alias_assign=1
fi
already_stashed=
stash "$1"
if [[ -z $already_stashed ]]; then
varname=${1%%'='*}
get_autostash_array_name
eval "$ret=(\$$ret \$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 ret
get_autostash_array_name
if (( ${#${(P)ret}} > 0 )); then
local run_from_autounstash=1
for autounstash_var in ${(P)ret}; do
unstash $autounstash_var
done
unset $ret
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=zsh autoindent expandtab shiftwidth=4 softtabstop=4