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