From f2e0bd786294198519854d234875f63a9c2e6e8e Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Tue, 8 Nov 2022 22:49:23 -0800 Subject: [PATCH 2/3] 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 1383b412ce7..f9796d69a9a 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -349,9 +349,9 @@ Built-ins @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 @@ Built-ins 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 @@ Built-ins 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 @@ Built-ins 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 @@ Variables @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 @@ Dollars Expansion 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 @@ Bugs and ideas @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 3338c06f037..ab1dcc59922 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 alises. 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 ac82e3f225c..2c721eb9e31 100644 --- a/lisp/eshell/em-cmpl.el +++ b/lisp/eshell/em-cmpl.el @@ -342,17 +342,23 @@ eshell-complete-parse-arguments (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 4b5e4dd53ed..3f7ec618a33 100644 --- a/lisp/eshell/em-unix.el +++ b/lisp/eshell/em-unix.el @@ -786,10 +786,14 @@ 'eshell-complete-hostname (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 cfec04e183d..0b175be713e 100644 --- a/lisp/eshell/esh-arg.el +++ b/lisp/eshell/esh-arg.el @@ -238,13 +238,53 @@ eshell-concat-1 (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 @@ eshell-operator "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 @@ eshell-parse-delimiter (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 4a41bbe8fa1..1fb84991120 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -480,11 +480,16 @@ eshell-rewrite-named-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 0ec11e8a0b3..aceca28befb 100644 --- a/lisp/eshell/esh-util.el +++ b/lisp/eshell/esh-util.el @@ -362,9 +362,13 @@ eshell-stringify-list "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 5824da6dc0e..61e9af01a4d 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 @@ eshell-interpolate-variable "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 @@ eshell-parse-variable 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 @@ eshell-parse-variable (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 @@ eshell-complete-variable-reference "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 aca622220e3..0a26e8d2011 100644 --- a/test/lisp/eshell/em-alias-tests.el +++ b/test/lisp/eshell/em-alias-tests.el @@ -72,6 +72,15 @@ em-alias-test/alias-all-args-var (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 96fde026a54..d95669fdaf8 100644 --- a/test/lisp/eshell/esh-var-tests.el +++ b/test/lisp/eshell/esh-var-tests.el @@ -60,6 +60,18 @@ esh-var-test/interp-quoted-var-concat (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 @@ esh-var-test/interp-var-length-alist (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 @@ esh-var-test/interp-concat-cmd-external (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 @@ esh-var-test/quoted-interp-quoted-var (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 @@ esh-var-test/quoted-interp-var-length-alist (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\\\")\"" @@ -324,6 +383,21 @@ esh-var-test/quoted-interp-concat-cmd (eshell-command-result-equal "echo \"${echo \\\"foo\nbar\\\"} baz\"" "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 -- 2.25.1