]> Sergey Matveev's repositories - zsh-autoenv.git/blob - lib/varstash
dd5096915e2bbb02939afb3d5628cb9f20ee0305
[zsh-autoenv.git] / lib / varstash
1 ################################################################################
2 # Stash/unstash support for per-directory variables
3 #
4 # Adopted for zsh-autoenv.
5 #
6 #   Copyright (c) 2009,2012 Dave Olszewski <cxreg@pobox.com>
7 #   http://github.com/cxreg/smartcd
8 #
9 #   This code is released under GPL v2 and the Artistic License, and
10 #   may be redistributed under the terms of either.
11 #
12 #
13 #   This library allows you to save the current value of a given environment
14 #   variable in a temporary location, so that you can modify it, and then
15 #   later restore its original value.
16 #
17 #   Note that you will need to be in the same directory you were in when you
18 #   stashed in order to successfully unstash.  This is because the temporary
19 #   variable is derived from your current working directory's path.
20 #
21 #   Usage:
22 #       stash PATH
23 #       export PATH=/something/else
24 #       [...]
25 #       unstash PATH
26 #
27 #   Note that this was written for use with, and works very well with,
28 #   smartcd.  See the documentation there for examples.
29 #
30 #   An alternate usage is `autostash' which will trigger autounstash when
31 #   leaving the directory, if combined with smartcd.  This reduces the amount
32 #   of explicit configuration you need to provide:
33 #
34 #       autostash PATH
35 #       export PATH=/something/else
36 #
37 #   You may also do both operations on line line, leaving only the very succinct
38 #
39 #       autostash PATH=/something/else
40 #
41 #   If you attempt to stash the same value twice, a warning will be displayed
42 #   and the second stash will not occur.  To make it happen anyway, pass -f
43 #   as the first argument to stash.
44 #
45 #       $ stash FOO
46 #       $ stash FOO
47 #       You have already stashed FOO, please specify "-f" if you want to overwrite another stashed value
48 #       $ stash -f FOO
49 #       $
50 #
51 #   This rule is a bit different if you are assigning a value and the variable
52 #   has already been stashed.  In that case, the new value will be assigned, but
53 #   the stash will not be overwritten.  This allows for non-conflicting chained
54 #   stash-assign rules.
55 #
56 ################################################################################
57
58
59 function stash() {
60     if [[ $1 == "-f" ]]; then
61         local force=1; shift
62     fi
63
64     while [[ -n $1 ]]; do
65         if [[ $1 == "alias" && $2 == *=* ]]; then
66             shift
67             local _stashing_alias_assign=1
68             continue
69         fi
70
71         local stash_expression=$1
72         local stash_which=${stash_expression%%'='*}
73         local stash_name=$(_mangle_var $stash_which)
74
75         # Extract the value and make it double-quote safe
76         local stash_value=${stash_expression#*'='}
77         stash_value=${stash_value//\\/\\\\}
78         stash_value=${stash_value//\"/\\\"}
79         stash_value=${stash_value//\`/\\\`}
80         stash_value=${stash_value//\$/\\\$}
81
82         if [[ ( -n "$(eval echo '$__varstash_alias__'$stash_name)"    ||
83                 -n "$(eval echo '$__varstash_function__'$stash_name)" ||
84                 -n "$(eval echo '$__varstash_array__'$stash_name)"    ||
85                 -n "$(eval echo '$__varstash_export__'$stash_name)"   ||
86                 -n "$(eval echo '$__varstash_variable__'$stash_name)" ||
87                 -n "$(eval echo '$__varstash_nostash__'$stash_name)" )
88                 && -z $force ]]; then
89
90             if [[ -z $already_stashed && ${already_stashed-_} == "_" ]]; then
91                 local already_stashed=1
92             else
93                 already_stashed=1
94             fi
95
96             if [[ $stash_which == $stash_expression ]]; then
97                 if [[ -z $run_from_smartcd ]]; then
98                     echo "You have already stashed $stash_which, please specify \"-f\" if you want to overwrite another stashed value"
99                 fi
100
101                 # Skip remaining work if we're not doing an assignment
102                 shift
103                 continue
104             fi
105         fi
106
107         # Handle any alias that may exist under this name
108         if [[ -z $already_stashed ]]; then
109             local alias_def="$(eval alias $stash_which 2>/dev/null)"
110             if [[ -n $alias_def ]]; then
111                 alias_def=${alias_def#alias }
112                 eval "__varstash_alias__$stash_name=\"$alias_def\""
113                 local stashed=1
114             fi
115         fi
116         if [[ $stash_which != $stash_expression && -n $_stashing_alias_assign ]]; then
117             eval "alias $stash_which=\"$stash_value\""
118         fi
119
120         # Handle any function that may exist under this name
121         if [[ -z $already_stashed ]]; then
122             local function_def="$(declare -f $stash_which)"
123             if [[ -n $function_def ]]; then
124                 # make function definition quote-safe.  because we are going to evaluate the
125                 # source with "echo -e", we need to double-escape the backslashes (so 1 -> 4)
126                 function_def=${function_def//\\/\\\\\\\\}
127                 function_def=${function_def//\"/\\\"}
128                 function_def=${function_def//\`/\\\`}
129                 function_def=${function_def//\$/\\\$}
130                 eval "__varstash_function__$stash_name=\"$function_def\""
131                 local stashed=1
132             fi
133         fi
134
135         # Handle any variable that may exist under this name
136         local vartype="$(declare -p $stash_which 2>/dev/null)"
137         if [[ -n $vartype ]]; then
138             if [[ -n $ZSH_VERSION ]]; then
139                 local pattern="typeset"
140             else
141                 local pattern="declare"
142             fi
143             if [[ $vartype == $pattern" -a"* ]]; then
144                 # varible is an array
145                 if [[ -z $already_stashed ]]; then
146                     eval "__varstash_array__$stash_name=(\"\${$stash_which""[@]}\")"
147                 fi
148
149             elif ([[ -n $ZSH_VERSION ]] && [[ $vartype == "export "* ]]) \
150                 || [[ $vartype == $pattern" -x"* ]]; then
151                 # variable is exported
152                 if [[ -z $already_stashed ]]; then
153                     eval "export __varstash_export__$stash_name=\"\$$stash_which\""
154                 fi
155                 if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
156                     eval "export $stash_which=\"$stash_value\""
157                 fi
158             else
159                 # regular variable
160                 if [[ -z $already_stashed ]]; then
161                     eval "__varstash_variable__$stash_name=\"\$$stash_which\""
162                 fi
163                 if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
164                     eval "$stash_which=\"$stash_value\""
165                 fi
166
167             fi
168             local stashed=1
169         fi
170
171         if [[ -z $stashed ]]; then
172             # Nothing in the variable we're stashing, but make a note that we stashed so we
173             # do the right thing when unstashing.  Without this, we take no action on unstash
174
175             # Zsh bug sometimes caues
176             # (eval):1: command not found: __varstash_nostash___tmp__home_dolszewski_src_smartcd_RANDOM_VARIABLE=1
177             # fixed in zsh commit 724fd07a67f, version 4.3.14
178             if [[ -z $already_stashed ]]; then
179                 eval "export __varstash_nostash__$stash_name=1"
180             fi
181
182             # In the case of a previously unset variable that we're assigning too, export it
183             if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
184                 eval "export $stash_which=\"$stash_value\""
185             fi
186         fi
187
188         shift
189         unset -v _stashing_alias_assign
190     done
191 }
192
193 function get_autostash_array_name() {
194     local autostash_name=$(_mangle_var AUTOSTASH)
195     # Create a scalar variable linked to an array (for exporting).
196     local autostash_array_name=${(L)autostash_name}
197     if ! (( ${(P)+autostash_array_name} )); then
198         # Conditionally set it, to prevent error with Zsh 4.3:
199         # can't tie already tied scalar: ...
200         typeset -xT $autostash_name $autostash_array_name
201     fi
202     ret=$autostash_array_name
203 }
204
205 function autostash() {
206     local run_from_autostash=1
207     while [[ -n $1 ]]; do
208         if [[ $1 == "alias" && $2 == *=* ]]; then
209             shift
210             local _stashing_alias_assign=1
211         fi
212
213         local already_stashed=
214         stash "$1"
215         if [[ -z $already_stashed ]]; then
216             local ret varname=${1%%'='*}
217             get_autostash_array_name
218             eval "$ret=(\$$ret \$varname)"
219         fi
220         shift
221         unset -v _stashing_alias_assign
222     done
223 }
224
225 function unstash() {
226     while [[ -n $1 ]]; do
227         local unstash_which=$1
228         if [[ -z $unstash_which ]]; then
229             continue
230         fi
231
232         local unstash_name=$(_mangle_var $unstash_which)
233
234         # This bit is a little tricky.  Here are the rules:
235         #   1) unstash any alias, function, or variable which matches
236         #   2) if one or more matches, but not all, delete any that did not
237         #   3) if none match but nostash is found, delete all
238         #   4) if none match and nostash not found, do nothing
239
240         # Unstash any alias
241         if [[ -n "$(eval echo \$__varstash_alias__$unstash_name)" ]]; then
242             eval "alias $(eval echo \$__varstash_alias__$unstash_name)"
243             unset __varstash_alias__$unstash_name
244             local unstashed=1
245             local unstashed_alias=1
246         fi
247
248         # Unstash any function
249         if [[ -n "$(eval echo \$__varstash_function__$unstash_name)" ]]; then
250             eval "function $(eval echo -e \"\$__varstash_function__$unstash_name\")"
251             unset __varstash_function__$unstash_name
252             local unstashed=1
253             local unstashed_function=1
254         fi
255
256         # Unstash any variable
257         if [[ -n "$(declare -p __varstash_array__$unstash_name 2>/dev/null)" ]]; then
258             eval "$unstash_which=(\"\${__varstash_array__$unstash_name""[@]}\")"
259             unset __varstash_array__$unstash_name
260             local unstashed=1
261             local unstashed_variable=1
262         elif [[ -n "$(declare -p __varstash_export__$unstash_name 2>/dev/null)" ]]; then
263             eval "export $unstash_which=\"\$__varstash_export__$unstash_name\""
264             unset __varstash_export__$unstash_name
265             local unstashed=1
266             local unstashed_variable=1
267         elif [[ -n "$(declare -p __varstash_variable__$unstash_name 2>/dev/null)" ]]; then
268             # Unset variable first to reset export
269             unset -v $unstash_which
270             eval "$unstash_which=\"\$__varstash_variable__$unstash_name\""
271             unset __varstash_variable__$unstash_name
272             local unstashed=1
273             local unstashed_variable=1
274         fi
275
276         # Unset any values which did not exist at time of stash
277         local nostash="$(eval echo \$__varstash_nostash__$unstash_name)"
278         unset __varstash_nostash__$unstash_name
279         if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_alias" ) ]]; then
280             unalias $unstash_which 2>/dev/null
281         fi
282         if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_function" ) ]]; then
283             unset -f $unstash_which 2>/dev/null
284         fi
285         if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_variable" ) ]]; then
286             # Don't try to unset illegal variable names
287             # Using substitution to avoid using regex, which might fail to load on Zsh (minimal system).
288             if [[ ${unstash_which//[^a-zA-Z0-9_]/} == $unstash_which && $unstash_which != [0-9]* ]]; then
289                 unset -v $unstash_which
290             fi
291         fi
292
293         shift
294     done
295 }
296
297 function autounstash() {
298     # If there is anything in (mangled) variable AUTOSTASH, then unstash it
299     local ret
300     get_autostash_array_name
301     if (( ${#${(P)ret}} > 0 )); then
302         local run_from_autounstash=1
303         for autounstash_var in ${(P)ret}; do
304             unstash $autounstash_var
305         done
306         unset $ret
307     fi
308 }
309
310 function _mangle_var() {
311     local mangle_var_where="${varstash_dir:-$PWD}"
312     mangle_var_where=${mangle_var_where//[^A-Za-z0-9]/_}
313     local mangled_name=${1//[^A-Za-z0-9]/_}
314     echo "_tmp_${mangle_var_where}_${mangled_name}"
315 }
316
317 # vim: filetype=zsh autoindent expandtab shiftwidth=4 softtabstop=4