Sync to HEAD
[bpt/emacs.git] / lisp / eshell / esh-var.el
CommitLineData
60370d40 1;;; esh-var.el --- handling of variables
affbf647 2
faadfb0a 3;; Copyright (C) 1999, 2000 Free Software Foundation
affbf647 4
7de5b421
GM
5;; Author: John Wiegley <johnw@gnu.org>
6
affbf647
GM
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation; either version 2, or (at your option)
12;; any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs; see the file COPYING. If not, write to the
21;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22;; Boston, MA 02111-1307, USA.
23
24(provide 'esh-var)
25
26(eval-when-compile (require 'esh-maint))
27
28(defgroup eshell-var nil
29 "Variable interpolation is introduced whenever the '$' character
30appears unquoted in any argument (except when that argument is
31surrounded by single quotes) . It may be used to interpolate a
32variable value, a subcommand, or even the result of a Lisp form."
33 :tag "Variable handling"
34 :group 'eshell)
35
36;;; Commentary:
37
38;; These are the possible variable interpolation syntaxes. Also keep
39;; in mind that if an argument looks like a number, it will be
40;; converted to a number. This is not significant when invoking
41;; external commands, but it's important when calling Lisp functions.
42;;
43;; $VARIABLE
44;;
45;; Interval the value of an environment variable, or a Lisp variable
46;;
47;; $ALSO-VAR
48;;
49;; "-" is a legal part of a variable name.
50;;
51;; $<MYVAR>-TOO
52;;
53;; Only "MYVAR" is part of the variable name in this case.
54;;
55;; $#VARIABLE
56;;
57;; Returns the length of the value of VARIABLE. This could also be
58;; done using the `length' Lisp function.
59;;
60;; $(lisp)
61;;
62;; Returns result of lisp evaluation. Note: Used alone like this, it
63;; is identical to just saying (lisp); but with the variable expansion
64;; form, the result may be interpolated a larger string, such as
65;; '$(lisp)/other'.
66;;
67;; ${command}
68;;
69;; Returns the value of an eshell subcommand. See the note above
70;; regarding Lisp evaluations.
71;;
72;; $ANYVAR[10]
73;;
74;; Return the 10th element of ANYVAR. If ANYVAR's value is a string,
75;; it will be split in order to make it a list. The splitting will
76;; occur at whitespace.
77;;
78;; $ANYVAR[: 10]
79;;
80;; As above, except that splitting occurs at the colon now.
81;;
82;; $ANYVAR[: 10 20]
83;;
84;; As above, but instead of returning just a string, it now returns a
85;; list of two strings. If the result is being interpolated into a
86;; larger string, this list will be flattened into one big string,
87;; with each element separated by a space.
88;;
89;; $ANYVAR["\\\\" 10]
90;;
91;; Separate on backslash characters. Actually, the first argument --
92;; if it doesn't have the form of a number, or a plain variable name
93;; -- can be any regular expression. So to split on numbers, use
94;; '$ANYVAR["[0-9]+" 10 20]'.
95;;
96;; $ANYVAR[hello]
97;;
98;; Calls `assoc' on ANYVAR with 'hello', expecting it to be an alist.
99;;
100;; $#ANYVAR[hello]
101;;
102;; Returns the length of the cdr of the element of ANYVAR who car is
103;; equal to "hello".
104;;
105;; There are also a few special variables defined by Eshell. '$$' is
106;; the value of the last command (t or nil, in the case of an external
107;; command). This makes it possible to chain results:
108;;
109;; /tmp $ echo /var/spool/mail/johnw
110;; /var/spool/mail/johnw
111;; /tmp $ dirname $$
112;; /var/spool/mail/
113;; /tmp $ cd $$
114;; /var/spool/mail $
115;;
116;; '$_' refers to the last argument of the last command. And $?
117;; contains the exit code of the last command (0 or 1 for Lisp
118;; functions, based on successful completion).
119
120(require 'env)
121(require 'ring)
122
123;;; User Variables:
124
125(defcustom eshell-var-load-hook '(eshell-var-initialize)
126 "*A list of functions to call when loading `eshell-var'."
127 :type 'hook
128 :group 'eshell-var)
129
130(defcustom eshell-prefer-lisp-variables nil
131 "*If non-nil, prefer Lisp variables to environment variables."
132 :type 'boolean
133 :group 'eshell-var)
134
135(defcustom eshell-complete-export-definition t
136 "*If non-nil, completing names for `export' shows current definition."
137 :type 'boolean
138 :group 'eshell-var)
139
6995786d
JW
140(defcustom eshell-modify-global-environment nil
141 "*If non-nil, using `export' changes Emacs's global environment."
142 :type 'boolean
143 :group 'eshell-var)
144
affbf647
GM
145(defcustom eshell-variable-name-regexp "[A-Za-z0-9_-]+"
146 "*A regexp identifying what constitutes a variable name reference.
147Note that this only applies for '$NAME'. If the syntax '$<NAME>' is
148used, then NAME can contain any character, including angle brackets,
149if they are quoted with a backslash."
150 :type 'regexp
151 :group 'eshell-var)
152
153(defcustom eshell-variable-aliases-list
154 '(;; for eshell.el
155 ("COLUMNS" (lambda (indices) (window-width)) t)
156 ("LINES" (lambda (indices) (window-height)) t)
157
158 ;; for eshell-cmd.el
159 ("_" (lambda (indices)
160 (if (not indices)
161 (car (last eshell-last-arguments))
162 (eshell-apply-indices eshell-last-arguments
163 indices))))
164 ("?" eshell-last-command-status)
165 ("$" eshell-last-command-result)
166 ("0" eshell-command-name)
167 ("1" (lambda (indices) (nth 0 eshell-command-arguments)))
168 ("2" (lambda (indices) (nth 1 eshell-command-arguments)))
169 ("3" (lambda (indices) (nth 2 eshell-command-arguments)))
170 ("4" (lambda (indices) (nth 3 eshell-command-arguments)))
171 ("5" (lambda (indices) (nth 4 eshell-command-arguments)))
172 ("6" (lambda (indices) (nth 5 eshell-command-arguments)))
173 ("7" (lambda (indices) (nth 6 eshell-command-arguments)))
174 ("8" (lambda (indices) (nth 7 eshell-command-arguments)))
175 ("9" (lambda (indices) (nth 8 eshell-command-arguments)))
176 ("*" (lambda (indices)
177 (if (not indices)
178 eshell-command-arguments
179 (eshell-apply-indices eshell-command-arguments
180 indices)))))
181 "*This list provides aliasing for variable references.
182It is very similar in concept to what `eshell-user-aliases-list' does
183for commands. Each member of this defines defines the name of a
184command, and the Lisp value to return for that variable if it is
185accessed via the syntax '$NAME'.
186
187If the value is a function, that function will be called with two
188arguments: the list of the indices that was used in the reference, and
189whether the user is requesting the length of the ultimate element.
190For example, a reference of '$NAME[10][20]' would result in the
191function for alias `NAME' being called (assuming it were aliased to a
192function), and the arguments passed to this function would be the list
193'(10 20)', and nil."
194 :type '(repeat (list string sexp
195 (choice (const :tag "Copy to environment" t)
196 (const :tag "Use only in Eshell" nil))))
197 :group 'eshell-var)
198
199(put 'eshell-variable-aliases-list 'risky-local-variable t)
200
201;;; Functions:
202
203(defun eshell-var-initialize ()
204 "Initialize the variable handle code."
205 ;; Break the association with our parent's environment. Otherwise,
206 ;; changing a variable will affect all of Emacs.
6995786d
JW
207 (unless eshell-modify-global-environment
208 (set (make-local-variable 'process-environment)
209 (eshell-copy-environment)))
affbf647
GM
210
211 (define-key eshell-command-map [(meta ?v)] 'eshell-insert-envvar)
212
213 (set (make-local-variable 'eshell-special-chars-inside-quoting)
214 (append eshell-special-chars-inside-quoting '(?$)))
215 (set (make-local-variable 'eshell-special-chars-outside-quoting)
216 (append eshell-special-chars-outside-quoting '(?$)))
217
affbf647
GM
218 (add-hook 'eshell-parse-argument-hook 'eshell-interpolate-variable t t)
219
affbf647
GM
220 (add-hook 'eshell-prepare-command-hook
221 'eshell-handle-local-variables nil t)
222
223 (when (eshell-using-module 'eshell-cmpl)
affbf647
GM
224 (add-hook 'pcomplete-try-first-hook
225 'eshell-complete-variable-reference nil t)
226 (add-hook 'pcomplete-try-first-hook
227 'eshell-complete-variable-assignment nil t)))
228
229(defun eshell-handle-local-variables ()
230 "Allow for the syntax 'VAR=val <command> <args>'."
231 ;; strip off any null commands, which can only happen if a variable
232 ;; evaluates to nil, such as "$var x", where `var' is nil. The
233 ;; command name in that case becomes `x', for compatibility with
234 ;; most regular shells (the difference is that they do an
235 ;; interpolation pass before the argument parsing pass, but Eshell
236 ;; does both at the same time).
237 (while (and (not eshell-last-command-name)
238 eshell-last-arguments)
239 (setq eshell-last-command-name (car eshell-last-arguments)
240 eshell-last-arguments (cdr eshell-last-arguments)))
241 (let ((setvar "\\`\\([A-Za-z_][A-Za-z0-9_]*\\)=\\(.*\\)\\'")
242 (command (eshell-stringify eshell-last-command-name))
243 (args eshell-last-arguments))
244 ;; local variable settings (such as 'CFLAGS=-O2 make') are handled
245 ;; by making the whole command into a subcommand, and calling
246 ;; setenv immediately before the command is invoked. This means
247 ;; that 'BLAH=x cd blah' won't work exactly as expected, but that
248 ;; is by no means a typical use of local environment variables.
249 (if (and command (string-match setvar command))
250 (throw
251 'eshell-replace-command
252 (list
253 'eshell-as-subcommand
254 (append
255 (list 'progn)
256 (let ((l (list t)))
257 (while (string-match setvar command)
258 (nconc
259 l (list
260 (list 'setenv (match-string 1 command)
261 (match-string 2 command)
262 (= (length (match-string 2 command)) 0))))
263 (setq command (eshell-stringify (car args))
264 args (cdr args)))
265 (cdr l))
266 (list (list 'eshell-named-command
267 command (list 'quote args)))))))))
268
269(defun eshell-interpolate-variable ()
270 "Parse a variable interpolation.
271This function is explicit for adding to `eshell-parse-argument-hook'."
272 (when (and (eq (char-after) ?$)
ca7aae91 273 (/= (1+ (point)) (point-max)))
affbf647
GM
274 (forward-char)
275 (list 'eshell-escape-arg
276 (eshell-parse-variable))))
277
278(defun eshell/define (var-alias definition)
012d3cb5 279 "Define a VAR-ALIAS using DEFINITION."
affbf647
GM
280 (if (not definition)
281 (setq eshell-variable-aliases-list
282 (delq (assoc var-alias eshell-variable-aliases-list)
283 eshell-variable-aliases-list))
284 (let ((def (assoc var-alias eshell-variable-aliases-list))
285 (alias-def
286 (list var-alias
287 (list 'quote (if (= (length definition) 1)
288 (car definition)
289 definition)))))
290 (if def
291 (setq eshell-variable-aliases-list
292 (delq (assoc var-alias eshell-variable-aliases-list)
293 eshell-variable-aliases-list)))
294 (setq eshell-variable-aliases-list
295 (cons alias-def
296 eshell-variable-aliases-list))))
297 nil)
298
299(defun eshell/export (&rest sets)
300 "This alias allows the 'export' command to act as bash users expect."
301 (while sets
ca7aae91
JW
302 (if (and (stringp (car sets))
303 (string-match "^\\([^=]+\\)=\\(.*\\)" (car sets)))
affbf647
GM
304 (setenv (match-string 1 (car sets))
305 (match-string 2 (car sets))))
306 (setq sets (cdr sets))))
307
79cf8e80
JW
308(defun pcomplete/eshell-mode/export ()
309 "Completion function for Eshell's `export'."
310 (while (pcomplete-here
311 (if eshell-complete-export-definition
312 process-environment
313 (eshell-envvar-names)))))
314
ca7aae91
JW
315(defun eshell/unset (&rest args)
316 "Unset an environment variable."
317 (while args
318 (if (stringp (car args))
319 (setenv (car args) nil t))
320 (setq args (cdr args))))
321
79cf8e80
JW
322(defun pcomplete/eshell-mode/unset ()
323 "Completion function for Eshell's `unset'."
324 (while (pcomplete-here (eshell-envvar-names))))
affbf647
GM
325
326(defun eshell/setq (&rest args)
327 "Allow command-ish use of `setq'."
328 (let (last-value)
329 (while args
330 (let ((sym (intern (car args)))
331 (val (cadr args)))
332 (setq last-value (set sym val)
333 args (cddr args))))
334 last-value))
335
336(defun pcomplete/eshell-mode/setq ()
337 "Completion function for Eshell's `setq'."
338 (while (and (pcomplete-here (all-completions pcomplete-stub
339 obarray 'boundp))
340 (pcomplete-here))))
341
342(defun eshell/env (&rest args)
343 "Implemention of `env' in Lisp."
344 (eshell-init-print-buffer)
345 (eshell-eval-using-options
346 "env" args
347 '((?h "help" nil nil "show this usage screen")
348 :external "env"
349 :usage "<no arguments>")
350 (eshell-for setting (sort (eshell-environment-variables)
351 'string-lessp)
352 (eshell-buffered-print setting "\n"))
353 (eshell-flush)))
354
355(defun eshell-insert-envvar (envvar-name)
356 "Insert ENVVAR-NAME into the current buffer at point."
357 (interactive
358 (list (read-envvar-name "Name of environment variable: " t)))
359 (insert-and-inherit "$" envvar-name))
360
361(defun eshell-envvar-names (&optional environment)
362 "Return a list of currently visible environment variable names."
363 (mapcar (function
364 (lambda (x)
365 (substring x 0 (string-match "=" x))))
366 (or environment process-environment)))
367
368(defun eshell-environment-variables ()
369 "Return a `process-environment', fully updated.
370This involves setting any variable aliases which affect the
371environment, as specified in `eshell-variable-aliases-list'."
372 (let ((process-environment (eshell-copy-environment)))
373 (eshell-for var-alias eshell-variable-aliases-list
374 (if (nth 2 var-alias)
375 (setenv (car var-alias)
376 (eshell-stringify
377 (or (eshell-get-variable (car var-alias)) "")))))
378 process-environment))
379
380(defun eshell-parse-variable ()
381 "Parse the next variable reference at point.
382The variable name could refer to either an environment variable, or a
383Lisp variable. The priority order depends on the setting of
384`eshell-prefer-lisp-variables'.
385
386Its purpose is to call `eshell-parse-variable-ref', and then to
387process any indices that come after the variable reference."
388 (let* ((get-len (when (eq (char-after) ?#)
389 (forward-char) t))
390 value indices)
391 (setq value (eshell-parse-variable-ref)
392 indices (and (not (eobp))
393 (eq (char-after) ?\[)
394 (eshell-parse-indices))
395 value (list 'let
396 (list (list 'indices
397 (list 'quote indices)))
398 value))
399 (if get-len
400 (list 'length value)
401 value)))
402
403(defun eshell-parse-variable-ref ()
404 "Eval a variable reference.
405Returns a Lisp form which, if evaluated, will return the value of the
406variable.
407
408Possible options are:
409
410 NAME an environment or Lisp variable value
411 <LONG-NAME> disambiguates the length of the name
412 {COMMAND} result of command is variable's value
413 (LISP-FORM) result of Lisp form is variable's value"
414 (let (end)
415 (cond
416 ((eq (char-after) ?{)
417 (let ((end (eshell-find-delimiter ?\{ ?\})))
418 (if (not end)
419 (throw 'eshell-incomplete ?\{)
420 (prog1
421 (list 'eshell-convert
422 (list 'eshell-command-to-value
423 (list 'eshell-as-subcommand
424 (eshell-parse-command
425 (cons (1+ (point)) end)))))
426 (goto-char (1+ end))))))
427 ((memq (char-after) '(?\' ?\"))
428 (let ((name (if (eq (char-after) ?\')
429 (eshell-parse-literal-quote)
430 (eshell-parse-double-quote))))
431 (if name
432 (list 'eshell-get-variable (eval name) 'indices))))
6b61353c 433 ((eq (char-after) ?\<)
affbf647
GM
434 (let ((end (eshell-find-delimiter ?\< ?\>)))
435 (if (not end)
436 (throw 'eshell-incomplete ?\<)
1ae720ac 437 (let* ((temp (make-temp-file temporary-file-directory))
affbf647
GM
438 (cmd (concat (buffer-substring (1+ (point)) end)
439 " > " temp)))
440 (prog1
441 (list
442 'let (list (list 'eshell-current-handles
443 (list 'eshell-create-handles temp
444 (list 'quote 'overwrite))))
445 (list
446 'progn
447 (list 'eshell-as-subcommand
448 (eshell-parse-command cmd))
449 (list 'ignore
450 (list 'nconc 'eshell-this-command-hook
451 (list 'list
452 (list 'function
453 (list 'lambda nil
454 (list 'delete-file temp))))))
455 (list 'quote temp)))
456 (goto-char (1+ end)))))))
457 ((eq (char-after) ?\()
458 (condition-case err
459 (list 'eshell-command-to-value
460 (list 'eshell-lisp-command
461 (list 'quote (read (current-buffer)))))
462 (end-of-file
463 (throw 'eshell-incomplete ?\())))
464 ((assoc (char-to-string (char-after))
465 eshell-variable-aliases-list)
466 (forward-char)
467 (list 'eshell-get-variable
468 (char-to-string (char-before)) 'indices))
469 ((looking-at eshell-variable-name-regexp)
470 (prog1
471 (list 'eshell-get-variable (match-string 0) 'indices)
472 (goto-char (match-end 0))))
473 (t
474 (error "Invalid variable reference")))))
475
476(eshell-deftest var interp-cmd
477 "Interpolate command result"
478 (eshell-command-result-p "+ ${+ 1 2} 3" "6\n"))
479
480(eshell-deftest var interp-lisp
481 "Interpolate Lisp form evalution"
482 (eshell-command-result-p "+ $(+ 1 2) 3" "6\n"))
483
484(eshell-deftest var interp-concat
485 "Interpolate and concat command"
486 (eshell-command-result-p "+ ${+ 1 2}3 3" "36\n"))
487
488(eshell-deftest var interp-concat-lisp
489 "Interpolate and concat Lisp form"
490 (eshell-command-result-p "+ $(+ 1 2)3 3" "36\n"))
491
492(eshell-deftest var interp-concat2
493 "Interpolate and concat two commands"
494 (eshell-command-result-p "+ ${+ 1 2}${+ 1 2} 3" "36\n"))
495
496(eshell-deftest var interp-concat-lisp2
497 "Interpolate and concat two Lisp forms"
498 (eshell-command-result-p "+ $(+ 1 2)$(+ 1 2) 3" "36\n"))
499
500(defun eshell-parse-indices ()
501 "Parse and return a list of list of indices."
502 (let (indices)
503 (while (eq (char-after) ?\[)
504 (let ((end (eshell-find-delimiter ?\[ ?\])))
505 (if (not end)
506 (throw 'eshell-incomplete ?\[)
507 (forward-char)
508 (let (eshell-glob-function)
509 (setq indices (cons (eshell-parse-arguments (point) end)
510 indices)))
511 (goto-char (1+ end)))))
512 (nreverse indices)))
513
514(defun eshell-get-variable (name &optional indices)
515 "Get the value for the variable NAME."
516 (let* ((alias (assoc name eshell-variable-aliases-list))
517 (var (if alias
518 (cadr alias)
519 name)))
520 (if (and alias (functionp var))
521 (funcall var indices)
522 (eshell-apply-indices
523 (cond
524 ((stringp var)
525 (let ((sym (intern-soft var)))
526 (if (and sym (boundp sym)
527 (or eshell-prefer-lisp-variables
528 (not (getenv var))))
529 (symbol-value sym)
530 (getenv var))))
531 ((symbolp var)
532 (symbol-value var))
533 (t
534 (error "Unknown variable `%s'" (eshell-stringify var))))
535 indices))))
536
537(defun eshell-apply-indices (value indices)
538 "Apply to VALUE all of the given INDICES, returning the sub-result.
539The format of INDICES is:
540
541 ((INT-OR-NAME-OR-OTHER INT-OR-NAME INT-OR-NAME ...)
542 ...)
543
544Each member of INDICES represents a level of nesting. If the first
545member of a sublist is not an integer or name, and the value it's
546reference is a string, that will be used as the regexp with which is
547to divide the string into sub-parts. The default is whitespace.
548Otherwise, each INT-OR-NAME refers to an element of the list value.
549Integers imply a direct index, and names, an associate lookup using
550`assoc'.
551
552For example, to retrieve the second element of a user's record in
553'/etc/passwd', the variable reference would look like:
554
555 ${egrep johnw /etc/passwd}[: 2]"
556 (while indices
557 (let ((refs (car indices)))
558 (when (stringp value)
559 (let (separator)
560 (if (not (or (not (stringp (caar indices)))
561 (string-match
562 (concat "^" eshell-variable-name-regexp "$")
563 (caar indices))))
564 (setq separator (caar indices)
565 refs (cdr refs)))
566 (setq value
567 (mapcar 'eshell-convert
568 (split-string value separator)))))
569 (cond
570 ((< (length refs) 0)
571 (error "Illegal array variable index: %s"
572 (eshell-stringify refs)))
573 ((= (length refs) 1)
574 (setq value (eshell-index-value value (car refs))))
575 (t
576 (let ((new-value (list t)))
577 (while refs
578 (nconc new-value
579 (list (eshell-index-value value
580 (car refs))))
581 (setq refs (cdr refs)))
582 (setq value (cdr new-value))))))
583 (setq indices (cdr indices)))
584 value)
585
586(defun eshell-index-value (value index)
587 "Reference VALUE using the given INDEX."
588 (if (stringp index)
589 (cdr (assoc index value))
590 (cond
591 ((ring-p value)
592 (if (> index (ring-length value))
593 (error "Index exceeds length of ring")
594 (ring-ref value index)))
595 ((listp value)
596 (if (> index (length value))
597 (error "Index exceeds length of list")
598 (nth index value)))
599 ((vectorp value)
600 (if (> index (length value))
601 (error "Index exceeds length of vector")
602 (aref value index)))
603 (t
604 (error "Invalid data type for indexing")))))
605
606;;;_* Variable name completion
607
608(defun eshell-complete-variable-reference ()
609 "If there is a variable reference, complete it."
610 (let ((arg (pcomplete-actual-arg)) index)
611 (when (setq index
612 (string-match
613 (concat "\\$\\(" eshell-variable-name-regexp
614 "\\)?\\'") arg))
615 (setq pcomplete-stub (substring arg (1+ index)))
616 (throw 'pcomplete-completions (eshell-variables-list)))))
617
618(defun eshell-variables-list ()
619 "Generate list of applicable variables."
620 (let ((argname pcomplete-stub)
621 completions)
622 (eshell-for alias eshell-variable-aliases-list
623 (if (string-match (concat "^" argname) (car alias))
624 (setq completions (cons (car alias) completions))))
625 (sort
626 (append
627 (mapcar
628 (function
629 (lambda (varname)
630 (let ((value (eshell-get-variable varname)))
631 (if (and value
632 (stringp value)
633 (file-directory-p value))
634 (concat varname (char-to-string directory-sep-char))
635 varname))))
636 (eshell-envvar-names (eshell-environment-variables)))
637 (all-completions argname obarray 'boundp)
638 completions)
639 'string-lessp)))
640
641(defun eshell-complete-variable-assignment ()
642 "If there is a variable assignment, allow completion of entries."
643 (let ((arg (pcomplete-actual-arg)) pos)
644 (when (string-match (concat "\\`" eshell-variable-name-regexp "=") arg)
645 (setq pos (match-end 0))
646 (if (string-match "\\(:\\)[^:]*\\'" arg)
647 (setq pos (match-end 1)))
648 (setq pcomplete-stub (substring arg pos))
649 (throw 'pcomplete-completions (pcomplete-entries)))))
650
651;;; Code:
652
6b61353c 653;;; arch-tag: 393654fe-bdad-4f27-9a10-b1472ded14cf
affbf647 654;;; esh-var.el ends here