diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index bbb6b2e6aac..9a2714b14fb 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index 4346fb4aedd..f9ba659ed86 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -257,6 +257,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 diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 65f997e5b88..c9096b0d159 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -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. diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el index 9e4cbc58201..0f388a9eba4 100644 --- a/test/lisp/eshell/esh-cmd-tests.el +++ b/test/lisp/eshell/esh-cmd-tests.el @@ -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"))