]> Sergey Matveev's repositories - zsh-autoenv.git/blob - lib/varstash
17bcd62e70b1c040ba4970f864b825f6230c759a
[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 # Library functions, from smartcd's lib/core/arrays. {{{
60 function apush() {
61     local var=$1; shift
62     eval "$var=(\${$var[@]} \"\$@\")"
63 }
64
65 function alen() {
66     local var=$1
67
68     if [[ -n $var ]]; then
69         eval "echo \${#$var[@]}"
70     fi
71 }
72
73 function afirst() {
74     setopt localoptions && setopt ksharrays
75     local var=$1
76
77     if [[ -n $var ]] && (( $(eval "echo \${#$var[@]}") >= 1 )); then
78         eval "echo \"\${$var""[0]}\""
79     fi
80 }
81
82 function ashift() {
83     setopt localoptions && setopt ksharrays
84     local var=$1
85
86     local _ashift_return=
87
88     if [[ -n $var ]] && (( $(eval "echo \${#$var[@]}") >= 1 )); then
89         eval "_ashift_return=\"\${$var""[0]}\""
90         eval "$var""[0]=()"
91
92         echo "$_ashift_return"
93     fi
94 }
95 # }}}
96
97
98 function stash() {
99     if [[ $1 == "-f" ]]; then
100         local force=1; shift
101     fi
102
103     while [[ -n $1 ]]; do
104         if [[ $1 == "alias" && $2 == *=* ]]; then
105             shift
106             local _stashing_alias_assign=1
107             continue
108         fi
109
110         local stash_expression=$1
111         local stash_which=${stash_expression%%'='*}
112         local stash_name=$(_mangle_var $stash_which)
113
114         # Extract the value and make it double-quote safe
115         local stash_value=${stash_expression#*'='}
116         stash_value=${stash_value//\\/\\\\}
117         stash_value=${stash_value//\"/\\\"}
118         stash_value=${stash_value//\`/\\\`}
119         stash_value=${stash_value//\$/\\\$}
120
121         if [[ ( -n "$(eval echo '$__varstash_alias__'$stash_name)"    ||
122                 -n "$(eval echo '$__varstash_function__'$stash_name)" ||
123                 -n "$(eval echo '$__varstash_array__'$stash_name)"    ||
124                 -n "$(eval echo '$__varstash_export__'$stash_name)"   ||
125                 -n "$(eval echo '$__varstash_variable__'$stash_name)" ||
126                 -n "$(eval echo '$__varstash_nostash__'$stash_name)" )
127                 && -z $force ]]; then
128
129             if [[ -z $already_stashed && ${already_stashed-_} == "_" ]]; then
130                 local already_stashed=1
131             else
132                 already_stashed=1
133             fi
134
135             if [[ $stash_which == $stash_expression ]]; then
136                 if [[ -z $run_from_smartcd ]]; then
137                     echo "You have already stashed $stash_which, please specify \"-f\" if you want to overwrite another stashed value"
138                 fi
139
140                 # Skip remaining work if we're not doing an assignment
141                 shift
142                 continue
143             fi
144         fi
145
146         # Handle any alias that may exist under this name
147         if [[ -z $already_stashed ]]; then
148             local alias_def="$(eval alias $stash_which 2>/dev/null)"
149             if [[ -n $alias_def ]]; then
150                 alias_def=${alias_def#alias }
151                 eval "__varstash_alias__$stash_name=\"$alias_def\""
152                 local stashed=1
153             fi
154         fi
155         if [[ $stash_which != $stash_expression && -n $_stashing_alias_assign ]]; then
156             eval "alias $stash_which=\"$stash_value\""
157         fi
158
159         # Handle any function that may exist under this name
160         if [[ -z $already_stashed ]]; then
161             local function_def="$(declare -f $stash_which)"
162             if [[ -n $function_def ]]; then
163                 # make function definition quote-safe.  because we are going to evaluate the
164                 # source with "echo -e", we need to double-escape the backslashes (so 1 -> 4)
165                 function_def=${function_def//\\/\\\\\\\\}
166                 function_def=${function_def//\"/\\\"}
167                 function_def=${function_def//\`/\\\`}
168                 function_def=${function_def//\$/\\\$}
169                 eval "__varstash_function__$stash_name=\"$function_def\""
170                 local stashed=1
171             fi
172         fi
173
174         # Handle any variable that may exist under this name
175         local vartype="$(declare -p $stash_which 2>/dev/null)"
176         if [[ -n $vartype ]]; then
177             if [[ -n $ZSH_VERSION ]]; then
178                 local pattern="typeset"
179             else
180                 local pattern="declare"
181             fi
182             if [[ $vartype == $pattern" -a"* ]]; then
183                 # varible is an array
184                 if [[ -z $already_stashed ]]; then
185                     eval "__varstash_array__$stash_name=(\"\${$stash_which""[@]}\")"
186                 fi
187
188             elif [[ $vartype == $pattern" -x"* ]]; then
189                 # variable is exported
190                 if [[ -z $already_stashed ]]; then
191                     eval "__varstash_export__$stash_name=\"\$$stash_which\""
192                 fi
193                 if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
194                     eval "export $stash_which=\"$stash_value\""
195                 fi
196             else
197                 # regular variable
198                 if [[ -z $already_stashed ]]; then
199                     eval "__varstash_variable__$stash_name=\"\$$stash_which\""
200                 fi
201                 if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
202                     eval "$stash_which=\"$stash_value\""
203                 fi
204
205             fi
206             local stashed=1
207         fi
208
209         if [[ -z $stashed ]]; then
210             # Nothing in the variable we're stashing, but make a note that we stashed so we
211             # do the right thing when unstashing.  Without this, we take no action on unstash
212
213             # Zsh bug sometimes caues
214             # (eval):1: command not found: __varstash_nostash___tmp__home_dolszewski_src_smartcd_RANDOM_VARIABLE=1
215             # fixed in zsh commit 724fd07a67f, version 4.3.14
216             if [[ -z $already_stashed ]]; then
217                 eval "__varstash_nostash__$stash_name=1"
218             fi
219
220             # In the case of a previously unset variable that we're assigning too, export it
221             if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
222                 eval "export $stash_which=\"$stash_value\""
223             fi
224         fi
225
226         shift
227         unset -v _stashing_alias_assign
228     done
229 }
230
231 function autostash() {
232     local run_from_autostash=1
233     while [[ -n $1 ]]; do
234         if [[ $1 == "alias" && $2 == *=* ]]; then
235             shift
236             local _stashing_alias_assign=1
237         fi
238
239         local already_stashed=
240         stash "$1"
241         if [[ -z $already_stashed ]]; then
242             local autostash_name=$(_mangle_var AUTOSTASH)
243             local varname=${1%%'='*}
244             apush $autostash_name "$varname"
245         fi
246         shift
247         unset -v _stashing_alias_assign
248     done
249 }
250
251 function unstash() {
252     while [[ -n $1 ]]; do
253         local unstash_which=$1
254         if [[ -z $unstash_which ]]; then
255             continue
256         fi
257
258         local unstash_name=$(_mangle_var $unstash_which)
259
260         # This bit is a little tricky.  Here are the rules:
261         #   1) unstash any alias, function, or variable which matches
262         #   2) if one or more matches, but not all, delete any that did not
263         #   3) if none match but nostash is found, delete all
264         #   4) if none match and nostash not found, do nothing
265
266         # Unstash any alias
267         if [[ -n "$(eval echo \$__varstash_alias__$unstash_name)" ]]; then
268             eval "alias $(eval echo \$__varstash_alias__$unstash_name)"
269             unset __varstash_alias__$unstash_name
270             local unstashed=1
271             local unstashed_alias=1
272         fi
273
274         # Unstash any function
275         if [[ -n "$(eval echo \$__varstash_function__$unstash_name)" ]]; then
276             eval "function $(eval echo -e \"\$__varstash_function__$unstash_name\")"
277             unset __varstash_function__$unstash_name
278             local unstashed=1
279             local unstashed_function=1
280         fi
281
282         # Unstash any variable
283         if [[ -n "$(declare -p __varstash_array__$unstash_name 2>/dev/null)" ]]; then
284             eval "$unstash_which=(\"\${__varstash_array__$unstash_name""[@]}\")"
285             unset __varstash_array__$unstash_name
286             local unstashed=1
287             local unstashed_variable=1
288         elif [[ -n "$(declare -p __varstash_export__$unstash_name 2>/dev/null)" ]]; then
289             eval "export $unstash_which=\"\$__varstash_export__$unstash_name\""
290             unset __varstash_export__$unstash_name
291             local unstashed=1
292             local unstashed_variable=1
293         elif [[ -n "$(declare -p __varstash_variable__$unstash_name 2>/dev/null)" ]]; then
294             # Unset variable first to reset export
295             unset -v $unstash_which
296             eval "$unstash_which=\"\$__varstash_variable__$unstash_name\""
297             unset __varstash_variable__$unstash_name
298             local unstashed=1
299             local unstashed_variable=1
300         fi
301
302         # Unset any values which did not exist at time of stash
303         local nostash="$(eval echo \$__varstash_nostash__$unstash_name)"
304         unset __varstash_nostash__$unstash_name
305         if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_alias" ) ]]; then
306             unalias $unstash_which 2>/dev/null
307         fi
308         if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_function" ) ]]; then
309             unset -f $unstash_which 2>/dev/null
310         fi
311         if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_variable" ) ]]; then
312             # Don't try to unset illegal variable names
313             # Using substitution to avoid using regex, which might fail to load on Zsh (minimal system).
314             if [[ ${unstash_which//[^a-zA-Z0-9_]/} == $unstash_which && $unstash_which != [0-9]* ]]; then
315                 unset -v $unstash_which
316             fi
317         fi
318
319         shift
320     done
321 }
322
323 function autounstash() {
324     # If there is anything in (mangled) variable AUTOSTASH, then unstash it
325     local autounstash_name=$(_mangle_var AUTOSTASH)
326     if (( $(alen $autounstash_name) > 0 )); then
327         local run_from_autounstash=1
328         while (( $(alen $autounstash_name) > 0 )); do
329             local autounstash_var=$(afirst $autounstash_name)
330             ashift $autounstash_name >/dev/null
331             unstash $autounstash_var
332         done
333         unset $autounstash_name
334     fi
335 }
336
337 function _mangle_var() {
338     local mangle_var_where="${varstash_dir:-$PWD}"
339     mangle_var_where=${mangle_var_where//[^A-Za-z0-9]/_}
340     local mangled_name=${1//[^A-Za-z0-9]/_}
341     echo "_tmp_${mangle_var_where}_${mangled_name}"
342 }
343
344 # vim: filetype=sh autoindent expandtab shiftwidth=4 softtabstop=4