]> git.eshelyaron.com Git - emacs.git/commitdiff
Improve the behavior of concatenating parts of Eshell arguments
authorJim Porter <jporterbugs@gmail.com>
Mon, 2 May 2022 23:56:49 +0000 (16:56 -0700)
committerLars Ingebrigtsen <larsi@gnus.org>
Tue, 3 May 2022 16:23:02 +0000 (18:23 +0200)
Previously, concatenating a list to a string would first convert the
list to a string.  Now, the string is concatenated with the last
element of the list.

* lisp/eshell/esh-util.el (eshell-to-flat-string): Make obsolete.

* lisp/eshell/esh-arg.el (eshell-concat, eshell-concat-1): New
functions.
(eshell-resolve-current-argument): Use 'eshell-concat'.

* test/lisp/eshell/esh-var-tests.el (esh-var-test/interp-concat-cmd):
Add check for concatenation of multiline output of subcommands.
(esh-var-test/quoted-interp-concat-cmd): New test.

* test/lisp/eshell/em-extpipe-tests.el (em-extpipe-test-13): Use
'eshell-concat'.

* doc/misc/eshell.texi (Expansion): Document this behavior.

* etc/NEWS: Announce the change (bug#55236).

doc/misc/eshell.texi
etc/NEWS
lisp/eshell/esh-arg.el
lisp/eshell/esh-util.el
test/lisp/eshell/em-extpipe-tests.el
test/lisp/eshell/esh-var-tests.el

index be32b2aced40b602cc862203e148cb51cc92fbba..dfb22bcb514bfb21696392a8c8e123bc2c63c6f6 100644 (file)
@@ -1017,11 +1017,37 @@ parsers (such as @command{cpp} and @command{m4}), but in a command
 shell, they are less often used for constants, and usually for using
 variables and string manipulation.@footnote{Eshell has no
 string-manipulation expansions because the Elisp library already
-provides many functions for this.}  For example, @code{$var} on a line
-expands to the value of the variable @code{var} when the line is
+provides many functions for this.}  For example, @code{$@var{var}} on
+a line expands to the value of the variable @var{var} when the line is
 executed.  Expansions are usually passed as arguments, but may also be
-used as commands.@footnote{E.g., entering just @samp{$var} at the prompt
-is equivalent to entering the value of @code{var} at the prompt.}
+used as commands.@footnote{E.g., entering just @samp{$@var{var}} at
+the prompt is equivalent to entering the value of @var{var} at the
+prompt.}
+
+You can concatenate expansions with regular string arguments or even
+other expansions.  In the simplest case, when the expansion returns a
+string value, this is equivalent to ordinary string concatenation; for
+example, @samp{$@{echo "foo"@}bar} returns @samp{foobar}.  The exact
+behavior depends on the types of each value being concatenated:
+
+@table @asis
+
+@item both strings
+Concatenate both values together.
+
+@item one or both numbers
+Concatenate the string representation of each value, converting back to
+a number if possible.
+
+@item one or both (non-@code{nil}) lists
+Concatenate ``adjacent'' elements of each value (possibly converting
+back to a number as above).  For example, @samp{$list("a" "b")c}
+returns @samp{("a" "bc")}.
+
+@item anything else
+Concatenate the string represenation of each value.
+
+@end table
 
 @menu
 * Dollars Expansion::
index 592b4b788868db2cfe2fed42b9035fedb3c5cf7b..15c7ce8a908ac5f04f3c2efd86a355b30bcc7f67 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1396,6 +1396,13 @@ If an Eshell expansion like '$FOO' is surrounded by double quotes, the
 result will always be a single string, no matter the type that would
 otherwise be returned.
 
++++
+*** Concatenating Eshell expansions now works more similarly to other shells.
+When concatenating an Eshell expansion that returns a list, "adjacent"
+elements of each operand are now concatenated together,
+e.g. '$list("a" "b")c' returns '("a" "bc")'.  See the "(eshell)
+Expansion" node in the Eshell manual for more details.
+
 +++
 *** Eshell subcommands with multiline numeric output return lists of numbers.
 If every line of the output of an Eshell subcommand like '${COMMAND}'
index 395aa87ff0ecda9c2c76022046d5a66cd6eb8d84..459487f43581193b02d28b0c09fb6d58373e5f31 100644 (file)
@@ -180,19 +180,63 @@ treated as a literal character."
       (add-text-properties 0 (length string) '(escaped t) string))
   string)
 
+(defun eshell-concat (quoted &rest rest)
+  "Concatenate all the arguments in REST and return the result.
+If QUOTED is nil, the resulting value(s) may be converted to
+numbers (see `eshell-concat-1').
+
+If each argument in REST is a non-list value, the result will be
+a single value, as if (mapconcat #'eshell-stringify REST) had been
+called, possibly converted to a number.
+
+If there is at least one (non-nil) list argument, the result will
+be a list, with \"adjacent\" elements of consecutive arguments
+concatenated as strings (again, possibly converted to numbers).
+For example, concatenating \"a\", (\"b\"), and (\"c\" \"d\")
+would produce (\"abc\" \"d\")."
+  (let (result)
+    (dolist (i rest result)
+      (when i
+        (cond
+         ((null result)
+          (setq result i))
+         ((listp result)
+          (let (curr-head curr-tail)
+            (if (listp i)
+                (setq curr-head (car i)
+                      curr-tail (cdr i))
+              (setq curr-head i
+                    curr-tail nil))
+            (setq result
+                  (append
+                   (butlast result 1)
+                   (list (eshell-concat-1 quoted (car (last result))
+                                          curr-head))
+                   curr-tail))))
+         ((listp i)
+          (setq result
+                (cons (eshell-concat-1 quoted result (car i))
+                      (cdr i))))
+         (t
+          (setq result (eshell-concat-1 quoted result i))))))))
+
+(defun eshell-concat-1 (quoted first second)
+  "Concatenate FIRST and SECOND.
+If QUOTED is nil and either FIRST or SECOND are numbers, try to
+convert the result to a number as well."
+  (let ((result (concat (eshell-stringify first) (eshell-stringify second))))
+    (if (and (not quoted)
+             (or (numberp first) (numberp second)))
+        (eshell-convert-to-number result)
+      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
-      (let ((parts eshell-current-argument))
-       (while parts
-         (unless (stringp (car parts))
-           (setcar parts
-                   (list 'eshell-to-flat-string (car parts))))
-         (setq parts (cdr parts)))
-       (setq eshell-current-argument
-             (list 'eshell-convert
-                   (append (list 'concat) eshell-current-argument))))
+      (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
index 9960912bce8c34d47dd7f051b32281e13a988a67..b5a423f02373ceab1462bb1d273b6e3a82a34d9d 100644 (file)
@@ -293,6 +293,7 @@ Prepend remote identification of `default-directory', if any."
 
 (defun eshell-to-flat-string (value)
   "Make value a string.  If separated by newlines change them to spaces."
+  (declare (obsolete nil "29.1"))
   (let ((text (eshell-stringify value)))
     (if (string-match "\n+\\'" text)
        (setq text (replace-match "" t t text)))
index 91c2fba4791d5d5dab49964afb536b95b14f3e68..3b84d763ac6a4438475d5b4e498fb97da1419f6d 100644 (file)
 
 (em-extpipe-tests--deftest em-extpipe-test-13 "foo*|bar"
   (should-parse '(eshell-execute-pipeline
-                  '((eshell-named-command (concat "foo" "*"))
+                  '((eshell-named-command (eshell-concat nil "foo" "*"))
                     (eshell-named-command "bar")))))
 
 (em-extpipe-tests--deftest em-extpipe-test-14 "tac *<temp"
index 2ce6bb4f1bac0f95731e94ba563c0b91c89068a1..3f3b591c5aee97496b1a0174bd640cdd0fcf95ba 100644 (file)
   (should (equal (eshell-test-command-result "+ $(+ 1 2)$(+ 1 2) 3") 36)))
 
 (ert-deftest esh-var-test/interp-concat-cmd ()
-  "Interpolate and concat command"
-  (should (equal (eshell-test-command-result "+ ${+ 1 2}3 3") 36)))
+  "Interpolate and concat command with literal"
+  (should (equal (eshell-test-command-result "+ ${+ 1 2}3 3") 36))
+  (should (equal (eshell-test-command-result "echo ${*echo \"foo\nbar\"}-baz")
+                 '("foo" "bar-baz")))
+  ;; Concatenating to a number in a list should produce a number...
+  (should (equal (eshell-test-command-result "echo ${*echo \"1\n2\"}3")
+                 '(1 23)))
+  ;; ... but concatenating to a string that looks like a number in a list
+  ;; should produce a string.
+  (should (equal (eshell-test-command-result "echo ${*echo \"hi\n2\"}3")
+                 '("hi" "23"))))
 
 (ert-deftest esh-var-test/interp-concat-cmd2 ()
   "Interpolate and concat two commands"
@@ -326,6 +335,12 @@ inside double-quotes"
   "Interpolate command result redirected to temp file inside double-quotes"
   (should (equal (eshell-test-command-result "cat \"$<echo hi>\"") "hi")))
 
+(ert-deftest esh-var-test/quoted-interp-concat-cmd ()
+  "Interpolate and concat command with literal"
+  (should (equal (eshell-test-command-result
+                  "echo \"${echo \\\"foo\nbar\\\"} baz\"")
+                 "foo\nbar baz")))
+
 \f
 ;; Interpolated variable conversion