Add support for chaining conditionals in Eshell
* 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.
This commit is contained in:
parent
40ffacb34b
commit
fada04cfc7
4 changed files with 73 additions and 24 deletions
|
@ -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
|
||||
|
|
14
etc/NEWS
14
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"))
|
||||
|
|
Loading…
Add table
Reference in a new issue