[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 ()