From 54124e128b5906425df5963ca99a8746f1dead43 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 15 Nov 2014 16:24:26 +0100 Subject: [PATCH] Add varstash from smartcd Source: https://github.com/cxreg/smartcd/blob/4fef3d3610873d9e8044cb5be1922fb056f4228d/lib/core/varstash --- lib/varstash | 408 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 lib/varstash diff --git a/lib/varstash b/lib/varstash new file mode 100644 index 0000000..c2e26c2 --- /dev/null +++ b/lib/varstash @@ -0,0 +1,408 @@ +################################################################################ +# Stash/unstash support for per-directory variables +# +# 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 run stash, unstash, or varstash interactively, they will instruct +# you on how to create smartcd scripts for performing those actions +# automatically. You can affect this behavior with several variables: +# +# VARSTASH_QUIET - Set if you'd rather not see these notices +# +# VARSTASH_AUTOCONFIG - Set if you want the suggested actions to be +# performed automatically +# +# VARSTASH_AUTOEDIT - Set if you want it to set the values, but also +# give you an opportunity to edit the file +# +# 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 + + if [[ -n $1 ]] && [[ -z $run_from_smartcd ]] && [[ -z $run_from_autostash ]]; then + local working_dir="${varstash_dir:-$(pwd)}" + local smartcd_dir="$(_smartcd_base)/scripts$working_dir" + local help_action="stashing a variable" + local help_dir=$smartcd_dir + local help_cmd="echo stash $@ >> \"$smartcd_dir/bash_enter\"" + local help_which="bash_enter" + _manual_stash_help + 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() { + if [[ -n $1 ]] && [[ -z $run_from_smartcd ]]; then + local working_dir="${varstash_dir:-$(pwd)}" + local smartcd_dir="$(_smartcd_base)/scripts$working_dir" + local help_action="autostashing a variable" + local help_dir=$smartcd_dir + local help_cmd="echo autostash $@ >> \"$smartcd_dir/bash_enter\"" + local help_which="bash_enter" + _manual_stash_help + fi + + 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() { + if [[ -n $1 ]] && [[ -z $run_from_smartcd ]] && [[ -z $run_from_autounstash ]]; then + local working_dir=${varstash_dir:-$(pwd)} + local smartcd_dir="$(_smartcd_base)/scripts$working_dir" + local help_action="unstashing a variable" + local help_dir=$smartcd_dir + local help_cmd="echo unstash $@ >> \"$smartcd_dir/bash_leave\"" + local help_which="bash_leave" + _manual_stash_help + fi + + 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}" +} + +function _manual_stash_help() { + # instruct user how to create bash_enter or bash_leave + if [[ -n $VARSTASH_AUTOEDIT || -n $VARSTASH_AUTOCONFIG ]]; then + if [[ -z $VARSTASH_QUIET ]]; then + echo "varstash: Automatically running $help_cmd" + fi + + if [[ ! -d $help_dir ]]; then + mkdir -p "$help_dir" + fi + eval $help_cmd + + if [[ -n $VARSTASH_AUTOEDIT ]]; then + varstash_edit $help_which + fi + elif [[ -z $VARSTASH_QUIET ]]; then + echo "############################################################################" + echo "# You are manually $help_action. To automatically perform this" + echo "# whenever you enter this directory, paste the following command(s):" + + if [[ ! -d $help_dir ]]; then + echo "mkdir -p \"$help_dir\"" + fi + echo "$help_cmd" + echo "############################################################################" + fi +} + +# A couple convenient aliases for smartcd_edit +function autostash_edit() { + varstash_edit "$@" +} + +function varstash_edit() { + local file="$1" + local dir="$2" + + if [[ -n $ZSH_VERSION ]]; then + if [[ $(type smartcd_edit) == "smartcd_edit is a shell function" ]]; then + local can_run=1 + fi + else + if [[ $(type -t smartcd_edit) == "function" ]]; then + local can_run=1 + fi + fi + + if [[ -n "$can_run" ]]; then + # XXX - no support for "--host" or "--system" with this (yet?) + _smartcd_file_check "$file" "global" "" edit "$dir" + else + echo "smartcd not loaded, cannot run smartcd_edit" + fi +} + +# Run deferred smartcd if we're waiting for it, and arrays is also loaded +if [[ -n "$smartcd_initially_deferred" && -n "$(fn_exists apush)" && -z "$SMARTCD_NOINITIAL" ]]; then + smartcd_skip_action=1 + smartcd_run_mainline=1 + smartcd cd + unset smartcd_skip_action + unset smartcd_initially_deferred +fi + +# vim: filetype=sh autoindent expandtab shiftwidth=4 softtabstop=4 -- 2.44.0