]> git.eshelyaron.com Git - emacs.git/commitdiff
Add support for chaining conditionals in Eshell
authorJim Porter <jporterbugs@gmail.com>
Sat, 5 Oct 2024 05:26:01 +0000 (22:26 -0700)
committerEshel Yaron <me@eshelyaron.com>
Thu, 17 Oct 2024 18:51:27 +0000 (20:51 +0200)
* lisp/eshell/esh-cmd.el (eshell-structure-basic-command): Check for the
presence of the conditional.  Allow any number of BODY forms.
(eshell-rewrite-if-command): Add support for 'else' keyword and chained
conditionals.

* test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test/if-else-statement):
Test 'else' keyword.
(esh-cmd-test/if-else-statement-chain): New test.

* doc/misc/eshell.texi (Control Flow): Document this change.

* etc/NEWS: Announce this change.

(cherry picked from commit fada04cfc788a486265c9da6636611986b48ae49)

doc/misc/eshell.texi
etc/NEWS
lisp/eshell/esh-cmd.el
test/lisp/eshell/esh-cmd-tests.el

index 8aa103741bcb0276f3327e06de153ded21053cff..feaa7584845d13ac1461af3fdd0912f425be6a93 100644 (file)
@@ -1698,16 +1698,29 @@ satisfied if the subcommand's exit status is 0.
 @table @code
 
 @item if @var{conditional} @var{true-subcommand}
-@itemx if @var{conditional} @var{true-subcommand} @var{false-subcommand}
+@itemx if @var{conditional} @var{true-subcommand} else @var{false-subcommand}
 Evaluate @var{true-subcommand} if @var{conditional} is satisfied;
 otherwise, evaluate @var{false-subcommand}.  Both @var{true-subcommand}
 and @var{false-subcommand} should be subcommands, as with
 @var{conditional}.
 
+You can also chain together @code{if}/@code{else} forms, for example:
+
+@example
+if @{[ -f file.txt ]@} @{
+  echo found file
+@} else if @{[ -f alternate.txt ]@} @{
+  echo found alternate
+@} else @{
+  echo not found!
+@}
+@end example
+
 @item unless @var{conditional} @var{false-subcommand}
-@itemx unless @var{conditional} @var{false-subcommand} @var{true-subcommand}
+@itemx unless @var{conditional} @var{false-subcommand} else @var{true-subcommand}
 Evaluate @var{false-subcommand} if @var{conditional} is not satisfied;
-otherwise, evaluate @var{true-subcommand}.
+otherwise, evaluate @var{true-subcommand}.  Like above, you can also
+chain together @code{unless}/@code{else} forms.
 
 @item while @var{conditional} @var{subcommand}
 Repeatedly evaluate @var{subcommand} so long as @var{conditional} is
index 392eb5cc5a3c4993f833968464f26506f29429e2..bb80e4170a5a253865b419da942725690f5240fb 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -206,6 +206,20 @@ These functions now take an optional ERROR-TARGET argument to control
 where to send the standard error output.  See the "(eshell) Entry
 Points" node in the Eshell manual for more details.
 
++++
+*** Conditional statements in Eshell now use an 'else' keyword.
+Eshell now prefers the following form when writing conditionals:
+
+    if {conditional} {true-subcommand} else {false-subcommand}
+
+The old form (without the 'else' keyword) is retained for compatibility.
+
++++
+*** You can now chain conditional statements in Eshell.
+When using the newly-preferred conditional form in Eshell, you can now
+chain together multiple 'if'/'else' statements.  For more information,
+see "(eshell) Control Flow" in the Eshell manual.
+
 +++
 *** Eshell's built-in 'wait' command now accepts a timeout.
 By passing '-t' or '--timeout', you can specify a maximum time to wait
index 65f997e5b88ca318ef9146d61ed72cf40088af35..c9096b0d159da700da7c219d33303644222c82a9 100644 (file)
@@ -551,12 +551,14 @@ implemented via rewriting, rather than as a function."
               ,body)
              (setq ,for-items (cdr ,for-items)))))))
 
-(defun eshell-structure-basic-command (func names keyword test body
-                                           &optional else)
+(defun eshell-structure-basic-command (func names keyword test &rest body)
   "With TERMS, KEYWORD, and two NAMES, structure a basic command.
 The first of NAMES should be the positive form, and the second the
 negative.  It's not likely that users should ever need to call this
 function."
+  (unless test
+    (error "Missing test for `%s' command" keyword))
+
   ;; If the test form is a subcommand, wrap it in `eshell-commands' to
   ;; silence the output.
   (when (memq (car test) '(eshell-as-subcommand eshell-lisp-command))
@@ -582,33 +584,39 @@ function."
       (setq test `(not ,test)))
 
   ;; Finally, create the form that represents this structured command.
-  `(,func ,test ,body ,else))
+  `(,func ,test ,@body))
 
 (defun eshell-rewrite-while-command (terms)
   "Rewrite a `while' command into its equivalent Eshell command form.
 Because the implementation of `while' relies upon conditional
 evaluation of its argument (i.e., use of a Lisp special form), it
 must be implemented via rewriting, rather than as a function."
-  (if (and (stringp (car terms))
-          (member (car terms) '("while" "until")))
-      (eshell-structure-basic-command
-       'while '("while" "until") (car terms)
-       (cadr terms)
-       (car (last terms)))))
+  (when (and (stringp (car terms))
+             (member (car terms) '("while" "until")))
+    (eshell-structure-basic-command
+     'while '("while" "until") (car terms)
+     (cadr terms)
+     (caddr terms))))
 
 (defun eshell-rewrite-if-command (terms)
   "Rewrite an `if' command into its equivalent Eshell command form.
 Because the implementation of `if' relies upon conditional
 evaluation of its argument (i.e., use of a Lisp special form), it
 must be implemented via rewriting, rather than as a function."
-  (if (and (stringp (car terms))
-          (member (car terms) '("if" "unless")))
-      (eshell-structure-basic-command
-       'if '("if" "unless") (car terms)
-       (cadr terms)
-       (car (last terms (if (= (length terms) 4) 2)))
-       (when (= (length terms) 4)
-         (car (last terms))))))
+  (when (and (stringp (car terms))
+             (member (car terms) '("if" "unless")))
+    (eshell-structure-basic-command
+     'if '("if" "unless") (car terms)
+     (cadr terms)
+     (caddr terms)
+     (if (equal (nth 3 terms) "else")
+         ;; If there's an "else" keyword, allow chaining together
+         ;; multiple "if" forms...
+         (or (eshell-rewrite-if-command (nthcdr 4 terms))
+             (nth 4 terms))
+       ;; ... otherwise, only allow a single "else" block (without the
+       ;; keyword) as before for compatibility.
+       (nth 3 terms)))))
 
 (defun eshell-set-exit-info (status &optional result)
   "Set the exit status and result for the last command.
index 9e4cbc58201c14b0f91ccd9b68f32c0c180d10f5..0f388a9eba4158ae83c96ee2c3c3cbde72731973 100644 (file)
@@ -427,11 +427,15 @@ processes correctly."
 (ert-deftest esh-cmd-test/if-else-statement ()
   "Test invocation of an if/else statement."
   (let ((eshell-test-value t))
-    (eshell-command-result-equal "if $eshell-test-value {echo yes} {echo no}"
-                                 "yes"))
+    (eshell-command-result-equal
+     "if $eshell-test-value {echo yes} {echo no}" "yes")
+    (eshell-command-result-equal
+     "if $eshell-test-value {echo yes} else {echo no}" "yes"))
   (let ((eshell-test-value nil))
-    (eshell-command-result-equal "if $eshell-test-value {echo yes} {echo no}"
-                                 "no")))
+    (eshell-command-result-equal
+     "if $eshell-test-value {echo yes} {echo no}" "no")
+    (eshell-command-result-equal
+     "if $eshell-test-value {echo yes} else {echo no}" "no")))
 
 (ert-deftest esh-cmd-test/if-else-statement-lisp-form ()
   "Test invocation of an if/else statement using a Lisp form."
@@ -474,6 +478,16 @@ This tests when `eshell-lisp-form-nil-is-failure' is nil."
   (eshell-command-result-equal "if {[ foo = bar ]} {echo yes} {echo no}"
                                "no"))
 
+(ert-deftest esh-cmd-test/if-else-statement-chain ()
+  "Test invocation of a chained if/else statement."
+  (dolist (case '((1 . "one") (2 . "two") (3 . "other")))
+    (let ((eshell-test-value (car case)))
+      (eshell-command-result-equal
+       (concat "if (= eshell-test-value 1) {echo one} "
+               "else if (= eshell-test-value 2) {echo two} "
+               "else {echo other}")
+       (cdr case)))))
+
 (ert-deftest esh-cmd-test/if-statement-pipe ()
   "Test invocation of an if statement piped to another command."
   (skip-unless (executable-find "rev"))