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