emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master e63ef66c57 2/3: Add support for the "splice operator" in Eshell


From: Jim Porter
Subject: master e63ef66c57 2/3: Add support for the "splice operator" in Eshell
Date: Fri, 16 Dec 2022 01:09:37 -0500 (EST)

branch: master
commit e63ef66c57ee74b24998a16b34949f67bbb73d8d
Author: Jim Porter <jporterbugs@gmail.com>
Commit: Jim Porter <jporterbugs@gmail.com>

    Add support for the "splice operator" in Eshell
    
    This allows splicing lists in-place in argument lists, which is
    particularly important when defining aliases using the '$*' special
    variable (bug#59960).
    
    * lisp/eshell/esh-var.el (eshell-parse-variable): Add support for the
    splice operator.
    (eshell-interpolate-variable): Let 'eshell-parse-variable' handle
    adding 'eshell-escape-arg'.
    (eshell-complete-variable-reference): Handle the splice operator.
    
    * lisp/eshell/esh-arg.el (eshell-concat-groups)
    (eshell-prepare-splice): New functions...
    (eshell-resolve-current-argument): ... use them.
    (eshell-splice-args): New function.
    
    * lisp/eshell/esh-cmd.el (eshell-rewrite-named-command): Handle
    'eshell-splice-args'.
    
    * lisp/eshell/esh-util.el (eshell-list-to-string): New function...
    (eshell-flatten-and-stringify): ... use it.
    
    * lisp/eshell/em-cmpl.el (eshell-complete-parse-arguments): Remove
    'eshell-splice-args' sigils in Eshell command forms so that we can
    perform completion on splice-expansions.
    
    * lisp/eshell/em-unix.el (eshell-complete-host-reference): Don't try
    to complete arguments containing "$@".
    
    * test/lisp/eshell/esh-var-tets.el (esh-var-test/interp-list-var)
    (esh-var-test/interp-list-var-concat, esh-var-test/interp-var-splice)
    (esh-var-test/interp-var-splice-concat)
    (esh-var-test/quoted-interp-list-var)
    (esh-var-test/quoted-interp-list-var-concat)
    (esh-var-test/quoted-interp-var-splice)
    (esh-var-test/quoted-interp-var-splice-concat): New tests.
    
    * test/lisp/eshell/em-alias-tests.el
    (em-alias-test/alias-all-args-var-splice): New test.
    
    * doc/misc/eshell.texi (Dollars Expansion): Explain the splice
    operator.
    (Aliases): Expand documentation and use '$@*'.
    (Built-ins, Bugs and Ideas): Use '$@*' where appropriate.
    
    * etc/NEWS: Announce this change.
---
 doc/misc/eshell.texi               | 100 +++++++++++++++++++++++++++----------
 etc/NEWS                           |  10 ++++
 lisp/eshell/em-cmpl.el             |  26 ++++++----
 lisp/eshell/em-unix.el             |  12 +++--
 lisp/eshell/esh-arg.el             |  77 ++++++++++++++++++++++++++--
 lisp/eshell/esh-cmd.el             |  15 ++++--
 lisp/eshell/esh-util.el            |   6 ++-
 lisp/eshell/esh-var.el             |  24 +++++++--
 test/lisp/eshell/em-alias-tests.el |   9 ++++
 test/lisp/eshell/esh-var-tests.el  |  74 +++++++++++++++++++++++++++
 10 files changed, 298 insertions(+), 55 deletions(-)

diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 1383b412ce..f9796d69a9 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -349,9 +349,9 @@ alias (@pxref{Aliases}).  Example:
 @example
 ~ $ which sudo
 eshell/sudo is a compiled Lisp function in `em-tramp.el'.
-~ $ alias sudo '*sudo $*'
+~ $ alias sudo '*sudo $@@*'
 ~ $ which sudo
-sudo is an alias, defined as "*sudo $*"
+sudo is an alias, defined as "*sudo $@@*"
 @end example
 
 @vindex eshell-prefer-lisp-functions
@@ -475,7 +475,7 @@ Manual}.
 
 If @code{eshell-plain-diff-behavior} is non-@code{nil}, then this
 command does not use Emacs's internal @code{diff}.  This is the same
-as using @samp{alias diff '*diff $*'}.
+as using @samp{alias diff '*diff $@@*'}.
 
 @item dirname
 @cmindex dirname
@@ -545,9 +545,9 @@ but use Emacs's internal @code{grep} instead.
 
 If @code{eshell-plain-grep-behavior} is non-@code{nil}, then these
 commands do not use Emacs's internal @code{grep}.  This is the same as
-using @samp{alias grep '*grep $*'}, though this setting applies to all
-of the built-in commands for which you would need to create a separate
-alias.
+using @samp{alias grep '*grep $@@*'}, though this setting applies to
+all of the built-in commands for which you would need to create a
+separate alias.
 
 @item history
 @cmindex history
@@ -603,7 +603,7 @@ Alias to Emacs's @code{locate} function, which simply runs 
the external
 
 If @code{eshell-plain-locate-behavior} is non-@code{nil}, then Emacs's
 internal @code{locate} is not used.  This is the same as using
-@samp{alias locate '*locate $*'}.
+@samp{alias locate '*locate $@@*'}.
 
 @item ls
 @cmindex ls
@@ -1027,25 +1027,47 @@ necessary.  Its value is 
@code{@var{emacs-version},eshell}.
 @node Aliases
 @section Aliases
 
-@vindex $*
-@findex eshell-expand-history-references
+@findex eshell-read-aliases-list
 Aliases are commands that expand to a longer input line.  For example,
-@command{ll} is a common alias for @code{ls -l}, and would be defined
-with the command invocation @kbd{alias ll 'ls -l $*'}; with this defined,
-running @samp{ll foo} in Eshell will actually run @samp{ls -l foo}.
-Aliases defined (or deleted) by the @command{alias} command are
-automatically written to the file named by @code{eshell-aliases-file},
-which you can also edit directly.  After doing so, use @w{@kbd{M-x
-eshell-read-aliases-list}} to load the edited aliases.
-
-@vindex $1, $2, @dots{}
+@command{ll} is a common alias for @code{ls -l}.  To define this alias
+in Eshell, you can use the command invocation @kbd{alias ll 'ls -l
+$@@*'}; with this defined, running @samp{ll foo} in Eshell will
+actually run @samp{ls -l foo}.  Aliases defined (or deleted) by the
+@command{alias} command are automatically written to the file named by
+@code{eshell-aliases-file}, which you can also edit directly.  After
+doing so, use @w{@kbd{M-x eshell-read-aliases-list}} to load the
+edited aliases.
+
 Note that unlike aliases in Bash, arguments must be handled
-explicitly.  Typically the alias definition would end in @samp{$*} to
-pass all arguments along.  More selective use of arguments via
-@samp{$1}, @samp{$2}, etc., is also possible.  For example,
+explicitly.  Within aliases, you can use the special variables
+@samp{$*}, @samp{$0}, @samp{$1}, @samp{$2}, etc. to refer to the
+arguments passed to the alias.
+
+@table @code
+
+@vindex $*
+@item $*
+This expands to the list of arguments passed to the alias.  For
+example, if you run @code{my-alias 1 2 3}, then @samp{$*} would be the
+list @code{(1 2 3)}.  Note that since this variable is a list, using
+@samp{$*} in an alias will pass this list as a single argument to the
+aliased command.  Therefore, when defining an alias, you should
+usually use @samp{$@@*} to pass all arguments along, splicing them
+into your argument list (@pxref{Dollars Expansion}).
+
+@vindex $0
+@item $0
+This expands to the name of the alias currently being executed.
+
+@vindex $1, $2, @dots{}, $9
+@item $1, $2, @dots{}, $9
+These variables expand to the nth argument (starting at 1) passed to
+the alias.  This lets you selectively use an alias's arguments, so
 @kbd{alias mcd 'mkdir $1 && cd $1'} would cause @kbd{mcd foo} to
 create and switch to a directory called @samp{foo}.
 
+@end table
+
 @node History
 @section History
 @cmindex history
@@ -1307,12 +1329,36 @@ to split the string.  @var{regexp} can be any form 
other than a
 number.  For example, @samp{$@var{var}[: 0]} will return the first
 element of a colon-delimited string.
 
+@cindex length operator, in variable expansion
 @item $#@var{expr}
-Expands to the length of the result of @var{expr}, an expression in
-one of the above forms.  For example, @samp{$#@var{var}} returns the
-length of the variable @var{var} and @samp{$#@var{var}[0]} returns the
-length of the first element of @var{var}.  Again, signals an error if
-the result of @var{expr} is not a string or a sequence.
+This is the @dfn{length operator}.  It expands to the length of the
+result of @var{expr}, an expression in one of the above forms.  For
+example, @samp{$#@var{var}} returns the length of the variable
+@var{var} and @samp{$#@var{var}[0]} returns the length of the first
+element of @var{var}.  Again, signals an error if the result of
+@var{expr} is not a string or a sequence.
+
+@cindex splice operator, in variable expansion
+@item $@@@var{expr}
+This is the @dfn{splice operator}.  It ``splices'' the elements of
+@var{expr} (an expression of one of the above forms) into the
+resulting list of arguments, much like the @samp{,@@} marker in Emacs
+Lisp (@pxref{Backquote, , , elisp, The Emacs Lisp Reference Manual}).
+The elements of @var{expr} become arguments at the same level as the
+other arguments around it.  For example, if @var{numbers} is the list
+@code{(1 2 3)}, then:
+
+@example
+@group
+~ $ echo 0 $numbers
+(0
+ (1 2 3))
+@end group
+@group
+~ $ echo 0 $@@numbers
+(0 1 2 3)
+@end group
+@end example
 
 @end table
 
@@ -2031,7 +2077,7 @@ Allow for a Bash-compatible syntax, such as:
 
 @example
 alias arg=blah
-function arg () @{ blah $* @}
+function arg () @{ blah $@@* @}
 @end example
 
 @item Pcomplete sometimes gets stuck
diff --git a/etc/NEWS b/etc/NEWS
index 9c60e444c0..af7f1050b7 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -71,6 +71,16 @@ switches for shortlogs, such as the one produced by 'C-x v 
L'.
 You can now configure how to display the "*buffer-selection*" buffer
 using this new option.  (Or set 'display-buffer-alist' directly.)
 
+** Eshell
+
++++
+*** New splice operator for Eshell dollar expansions.
+Dollar expansions in Eshell now let you splice the elements of the
+expansion in-place using '$@expr'.  This makes it easier to fill lists
+of arguments into a command, such as when defining aliases.  For more
+information, see the "(eshell) Dollars Expansion" node in the Eshell
+manual.
+
 +++
 *** 'eshell-read-aliases-list' is now an interactive command.
 After manually editing 'eshell-aliases-file', you can use
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index ac82e3f225..2c721eb9e3 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -342,17 +342,23 @@ to writing a completion function."
        (setq pos (1+ pos))))
     (setq posns (cdr posns))
     (cl-assert (= (length args) (length posns)))
-    (let ((a args)
-         (i 0)
-         l)
+    (let ((a args) (i 0) new-start)
       (while a
-       (if (and (consp (car a))
-                (eq (caar a) 'eshell-operator))
-           (setq l i))
-       (setq a (cdr a) i (1+ i)))
-      (and l
-          (setq args (nthcdr (1+ l) args)
-                posns (nthcdr (1+ l) posns))))
+        ;; Remove any top-level `eshell-splice-args' sigils.  These
+        ;; are meant to be rewritten and can't actually be called.
+        (when (and (consp (car a))
+                   (eq (caar a) 'eshell-splice-args))
+          (setcar a (cadar a)))
+        ;; If there's an unreplaced `eshell-operator' sigil, consider
+        ;; the token after it the new start of our arguments.
+        (when (and (consp (car a))
+                   (eq (caar a) 'eshell-operator))
+          (setq new-start i))
+        (setq a (cdr a)
+              i (1+ i)))
+      (when new-start
+       (setq args (nthcdr (1+ new-start) args)
+             posns (nthcdr (1+ new-start) posns))))
     (cl-assert (= (length args) (length posns)))
     (when (and args (eq (char-syntax (char-before end)) ? )
               (not (eq (char-before (1- end)) ?\\)))
diff --git a/lisp/eshell/em-unix.el b/lisp/eshell/em-unix.el
index 4b5e4dd53e..3f7ec618a3 100644
--- a/lisp/eshell/em-unix.el
+++ b/lisp/eshell/em-unix.el
@@ -786,10 +786,14 @@ external command."
 
 (defun eshell-complete-host-reference ()
   "If there is a host reference, complete it."
-  (let ((arg (pcomplete-actual-arg))
-       index)
-    (when (setq index (string-match "@[a-z.]*\\'" arg))
-      (setq pcomplete-stub (substring arg (1+ index))
+  (let ((arg (pcomplete-actual-arg)))
+    (when (string-match
+           (rx ;; Match an "@", but not immediately following a "$".
+               (or string-start (not "$")) "@"
+               (group (* (any "a-z.")))
+               string-end)
+           arg)
+      (setq pcomplete-stub (substring arg (match-beginning 1))
            pcomplete-last-completion-raw t)
       (throw 'pcomplete-completions (pcomplete-read-host-names)))))
 
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index cfec04e183..0b175be713 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -238,13 +238,53 @@ convert the result to a number as well."
         (eshell-convert-to-number result)
       result)))
 
+(defun eshell-concat-groups (quoted &rest args)
+  "Concatenate groups of arguments in ARGS and return the result.
+QUOTED is passed to `eshell-concat' (which see) and, if non-nil,
+allows values to be converted to numbers where appropriate.
+
+ARGS should be a list of lists of arguments, such as that
+produced by `eshell-prepare-slice'.  \"Adjacent\" values of
+consecutive arguments will be passed to `eshell-concat'.  For
+example, if ARGS is
+
+  ((list a) (list b) (list c d e) (list f g)),
+
+then the result will be:
+
+  ((eshell-concat QUOTED a b c)
+   d
+   (eshell-concat QUOTED e f)
+   g)."
+  (let (result current-arg)
+    (dolist (arg args)
+      (when arg
+        (push (car arg) current-arg)
+        (when (length> arg 1)
+          (push (apply #'eshell-concat quoted (nreverse current-arg))
+                result)
+          (dolist (inner (butlast (cdr arg)))
+            (push inner result))
+          (setq current-arg (list (car (last arg)))))))
+    (when current-arg
+      (push (apply #'eshell-concat quoted (nreverse current-arg))
+            result))
+    (nreverse result)))
+
 (defun eshell-resolve-current-argument ()
   "If there are pending modifications to be made, make them now."
   (when eshell-current-argument
     (when eshell-arg-listified
-      (setq eshell-current-argument
-            (append (list 'eshell-concat eshell-current-quoted)
-                    eshell-current-argument))
+      (if-let ((grouped-terms (eshell-prepare-splice
+                               eshell-current-argument)))
+          (setq eshell-current-argument
+                `(eshell-splice-args
+                  (eshell-concat-groups ,eshell-current-quoted
+                                        ,@grouped-terms)))
+        ;; If no terms are spliced, use a simpler command form.
+        (setq eshell-current-argument
+              (append (list 'eshell-concat eshell-current-quoted)
+                      eshell-current-argument)))
       (setq eshell-arg-listified nil))
     (while eshell-current-modifiers
       (setq eshell-current-argument
@@ -348,6 +388,10 @@ Point is left at the end of the arguments."
   "A stub function that generates an error if a floating operator is found."
   (error "Unhandled operator in input text"))
 
+(defsubst eshell-splice-args (&rest _args)
+  "A stub function that generates an error if a floating splice is found."
+  (error "Splice operator is not permitted in this context"))
+
 (defsubst eshell-looking-at-backslash-return (pos)
   "Test whether a backslash-return sequence occurs at POS."
   (and (eq (char-after pos) ?\\)
@@ -500,5 +544,32 @@ If the form has no `type', the syntax is parsed as if 
`type' were
                   (char-to-string (char-after)))))
         (goto-char end)))))))
 
+(defun eshell-prepare-splice (args)
+  "Prepare a list of ARGS for splicing, if any arg requested a splice.
+This looks for `eshell-splice-args' as the CAR of each argument,
+and if found, returns a grouped list like:
+
+  ((list arg-1) (list arg-2) spliced-arg-3 ...)
+
+This allows callers of this function to build the final spliced
+list by concatenating each element together, e.g. with (apply
+#'append grouped-list).
+
+If no argument requested a splice, return nil."
+  (let* ((splicep nil)
+         ;; Group each arg like ((list arg-1) (list arg-2) ...),
+         ;; splicing in `eshell-splice-args' args.  This lets us
+         ;; apply spliced args correctly elsewhere.
+         (grouped-args
+          (mapcar (lambda (i)
+                    (if (eq (car-safe i) 'eshell-splice-args)
+                        (progn
+                          (setq splicep t)
+                          (cadr i))
+                      `(list ,i)))
+                  args)))
+    (when splicep
+      grouped-args)))
+
 (provide 'esh-arg)
 ;;; esh-arg.el ends here
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 4a41bbe8fa..1fb8499112 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -480,11 +480,16 @@ hooks should be run before and after the command."
   (let ((sym (if eshell-in-pipeline-p
                 'eshell-named-command*
               'eshell-named-command))
-       (cmd (car terms))
-       (args (cdr terms)))
-    (if args
-       (list sym cmd `(list ,@(cdr terms)))
-      (list sym cmd))))
+        (grouped-terms (eshell-prepare-splice terms)))
+    (cond
+     (grouped-terms
+      `(let ((terms (nconc ,@grouped-terms)))
+         (,sym (car terms) (cdr terms))))
+     ;; If no terms are spliced, use a simpler command form.
+     ((cdr terms)
+      (list sym (car terms) `(list ,@(cdr terms))))
+     (t
+      (list sym (car terms))))))
 
 (defvar eshell-command-body)
 (defvar eshell-test-body)
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index 0ec11e8a0b..aceca28bef 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -362,9 +362,13 @@ Prepend remote identification of `default-directory', if 
any."
   "Convert each element of ARGS into a string value."
   (mapcar #'eshell-stringify args))
 
+(defsubst eshell-list-to-string (list)
+  "Convert LIST into a single string separated by spaces."
+  (mapconcat #'eshell-stringify list " "))
+
 (defsubst eshell-flatten-and-stringify (&rest args)
   "Flatten and stringify all of the ARGS into a single string."
-  (mapconcat #'eshell-stringify (flatten-tree args) " "))
+  (eshell-list-to-string (flatten-tree args)))
 
 (defsubst eshell-directory-files (regexp &optional directory)
   "Return a list of files in the given DIRECTORY matching REGEXP."
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index 5824da6dc0..61e9af01a4 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -86,6 +86,13 @@
 ;; Returns the length of the value of $EXPR.  This could also be
 ;; done using the `length' Lisp function.
 ;;
+;;   $@EXPR
+;;
+;; Splices the value of $EXPR in-place into the current list of
+;; arguments.  This is analogous to the `,@' token in Elisp
+;; backquotes, and works as if the user typed '$EXPR[0] $EXPR[1]
+;; ... $EXPR[N]'.
+;;
 ;; There are also a few special variables defined by Eshell.  '$$' is
 ;; the value of the last command (t or nil, in the case of an external
 ;; command).  This makes it possible to chain results:
@@ -320,10 +327,9 @@ copied (a.k.a. \"exported\") to the environment of created 
subprocesses."
   "Parse a variable interpolation.
 This function is explicit for adding to `eshell-parse-argument-hook'."
   (when (and (eq (char-after) ?$)
-            (/= (1+ (point)) (point-max)))
+             (/= (1+ (point)) (point-max)))
     (forward-char)
-    (list 'eshell-escape-arg
-         (eshell-parse-variable))))
+    (eshell-parse-variable)))
 
 (defun eshell/define (var-alias definition)
   "Define a VAR-ALIAS using DEFINITION."
@@ -453,6 +459,8 @@ Its purpose is to call `eshell-parse-variable-ref', and 
then to
 process any indices that come after the variable reference."
   (let* ((get-len (when (eq (char-after) ?#)
                    (forward-char) t))
+         (splice (when (eq (char-after) ?@)
+                   (forward-char) t))
         value indices)
     (setq value (eshell-parse-variable-ref get-len)
          indices (and (not (eobp))
@@ -464,7 +472,13 @@ process any indices that come after the variable 
reference."
     (when get-len
       (setq value `(length ,value)))
     (when eshell-current-quoted
-      (setq value `(eshell-stringify ,value)))
+      (if splice
+          (setq value `(eshell-list-to-string ,value)
+                splice nil)
+        (setq value `(eshell-stringify ,value))))
+    (setq value `(eshell-escape-arg ,value))
+    (when splice
+      (setq value `(eshell-splice-args ,value)))
     value))
 
 (defun eshell-parse-variable-ref (&optional modifier-p)
@@ -753,7 +767,7 @@ For example, to retrieve the second element of a user's 
record in
   "If there is a variable reference, complete it."
   (let ((arg (pcomplete-actual-arg)))
     (when (string-match
-           (rx "$" (? "#")
+           (rx "$" (? (or "#" "@"))
                (? (group (regexp eshell-variable-name-regexp)))
                string-end)
            arg)
diff --git a/test/lisp/eshell/em-alias-tests.el 
b/test/lisp/eshell/em-alias-tests.el
index aca622220e..0a26e8d201 100644
--- a/test/lisp/eshell/em-alias-tests.el
+++ b/test/lisp/eshell/em-alias-tests.el
@@ -72,6 +72,15 @@
    (eshell-match-command-output "show-all-args a" "a\n")
    (eshell-match-command-output "show-all-args a b c" "a\nb\nc\n")))
 
+(ert-deftest em-alias-test/alias-all-args-var-splice ()
+  "Test alias with splicing the $* variable"
+  (with-temp-eshell
+   (eshell-insert-command "alias show-all-args 'echo args: $@*'")
+   (eshell-match-command-output "show-all-args" "args:\n")
+   (eshell-match-command-output "show-all-args a" "(\"args:\" \"a\")\n")
+   (eshell-match-command-output "show-all-args a b c"
+                                "(\"args:\" \"a\" \"b\" \"c\")\n")))
+
 (ert-deftest em-alias-test/alias-all-args-var-indices ()
   "Test alias with the $* variable using indices"
   (with-temp-eshell
diff --git a/test/lisp/eshell/esh-var-tests.el 
b/test/lisp/eshell/esh-var-tests.el
index 96fde026a5..d95669fdaf 100644
--- a/test/lisp/eshell/esh-var-tests.el
+++ b/test/lisp/eshell/esh-var-tests.el
@@ -60,6 +60,18 @@
   (eshell-command-result-equal "echo $\"user-login-name\"-foo"
                                (concat user-login-name "-foo")))
 
+(ert-deftest esh-var-test/interp-list-var ()
+  "Interpolate list variable"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo $eshell-test-value"
+                                 '(1 2 3))))
+
+(ert-deftest esh-var-test/interp-list-var-concat ()
+  "Interpolate and concat list variable"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo a$'eshell-test-value'z"
+                                 '("a1" 2 "3z"))))
+
 (ert-deftest esh-var-test/interp-var-indices ()
   "Interpolate list variable with indices"
   (let ((eshell-test-value '("zero" "one" "two" "three" "four")))
@@ -131,6 +143,26 @@
     (eshell-command-result-equal "echo $#eshell-test-value" 1)
     (eshell-command-result-equal "echo $#eshell-test-value[foo]" 3)))
 
+(ert-deftest esh-var-test/interp-var-splice ()
+  "Splice-interpolate list variable"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo a $@eshell-test-value z"
+                                 '("a" 1 2 3 "z"))))
+
+(ert-deftest esh-var-test/interp-var-splice-concat ()
+  "Splice-interpolate and concat list variable"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo it is a$@'eshell-test-value'z"
+                                 '("it" "is" "a1" 2 "3z"))
+    ;; This is a tricky case.  We're concatenating a spliced list and
+    ;; a non-spliced list.  The general rule is that splicing should
+    ;; work as though the user typed "$X[0] $X[1] ... $X[N]".  That
+    ;; means that the last value of our splice should get concatenated
+    ;; into the first value of the non-spliced list.
+    (eshell-command-result-equal
+     "echo it is $@'eshell-test-value'$eshell-test-value"
+     '("it" "is" 1 2 (31 2 3)))))
+
 (ert-deftest esh-var-test/interp-lisp ()
   "Interpolate Lisp form evaluation"
   (eshell-command-result-equal "+ $(+ 1 2) 3" 6))
@@ -197,6 +229,9 @@
    (eshell-match-command-output "echo ${echo hi}-${*echo there}"
                                 "hi-there\n")))
 
+
+;; Quoted variable interpolation
+
 (ert-deftest esh-var-test/quoted-interp-var ()
   "Interpolate variable inside double-quotes"
   (eshell-command-result-equal "echo \"$user-login-name\""
@@ -209,6 +244,18 @@
   (eshell-command-result-equal "echo \"hi, $\\\"user-login-name\\\"\""
                                (concat "hi, " user-login-name)))
 
+(ert-deftest esh-var-test/quoted-interp-list-var ()
+  "Interpolate list variable inside double-quotes"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo \"$eshell-test-value\""
+                                 "(1 2 3)")))
+
+(ert-deftest esh-var-test/quoted-interp-list-var-concat ()
+  "Interpolate and concat list variable inside double-quotes"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo \"a$'eshell-test-value'z\""
+                                 "a(1 2 3)z")))
+
 (ert-deftest esh-var-test/quoted-interp-var-indices ()
   "Interpolate string variable with indices inside double-quotes"
   (let ((eshell-test-value '("zero" "one" "two" "three" "four")))
@@ -291,6 +338,18 @@ inside double-quotes"
     (eshell-command-result-equal "echo \"$#eshell-test-value[foo]\""
                                  "3")))
 
+(ert-deftest esh-var-test/quoted-interp-var-splice ()
+  "Splice-interpolate list variable inside double-quotes"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo a \"$@eshell-test-value\" z"
+                                 '("a" "1 2 3" "z"))))
+
+(ert-deftest esh-var-test/quoted-interp-var-splice-concat ()
+  "Splice-interpolate and concat list variable inside double-quotes"
+  (let ((eshell-test-value '(1 2 3)))
+    (eshell-command-result-equal "echo \"a$@'eshell-test-value'z\""
+                                 "a1 2 3z")))
+
 (ert-deftest esh-var-test/quoted-interp-lisp ()
   "Interpolate Lisp form evaluation inside double-quotes"
   (eshell-command-result-equal "echo \"hi $(concat \\\"the\\\" \\\"re\\\")\""
@@ -325,6 +384,21 @@ inside double-quotes"
                                "foo\nbar baz"))
 
 
+;; Interpolating commands
+
+(ert-deftest esh-var-test/command-interp ()
+  "Interpolate a variable as a command name"
+  (let ((eshell-test-value "printnl"))
+    (eshell-command-result-equal "$eshell-test-value hello there"
+                                 "hello\nthere\n")))
+
+(ert-deftest esh-var-test/command-interp-splice ()
+  "Interpolate a splice variable as a command name with arguments"
+  (let ((eshell-test-value '("printnl" "hello" "there")))
+    (eshell-command-result-equal "$@eshell-test-value"
+                                 "hello\nthere\n")))
+
+
 ;; Interpolated variable conversion
 
 (ert-deftest esh-var-test/interp-convert-var-number ()



reply via email to

[Prev in Thread] Current Thread [Next in Thread]