[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
master 30320d9420 1/3: Only set Eshell execution result metavariables wh
From: |
Jim Porter |
Subject: |
master 30320d9420 1/3: Only set Eshell execution result metavariables when non-nil |
Date: |
Sat, 13 Aug 2022 01:13:05 -0400 (EDT) |
branch: master
commit 30320d9420b2850341e94fa1b10476344bfa9589
Author: Jim Porter <jporterbugs@gmail.com>
Commit: Jim Porter <jporterbugs@gmail.com>
Only set Eshell execution result metavariables when non-nil
This simplifies usage of 'eshell-close-handles' in several places and
makes it work more like the docstring indicated it would.
* lisp/eshell/esh-io.el (eshell-close-handles): Only store EXIT-CODE
and RESULT if they're non-nil. Also, use 'dotimes' and 'dolist' to
simplify the implementation.
* lisp/eshell/em-alias.el (eshell-write-aliases-list):
* lisp/eshell/esh-cmd.el (eshell-rewrite-for-command)
(eshell-structure-basic-command): Adapt calls to
'eshell-close-handles'.
* test/lisp/eshell/eshell-tests.el (eshell-test/simple-command-result)
(eshell-test/lisp-command, eshell-test/lisp-command-with-quote)
(eshell-test/for-loop, eshell-test/for-name-loop)
(eshell-test/for-name-shadow-loop, eshell-test/lisp-command-args)
(eshell-test/subcommand, eshell-test/subcommand-args)
(eshell-test/subcommand-lisp): Move from here...
* test/lisp/eshell/esh-cmd-tests.el
(esh-cmd-test/simple-command-result, esh-cmd-test/lisp-command)
(esh-cmd-test/lisp-command-with-quote, esh-cmd-test/for-loop)
(esh-cmd-test/for-name-loop, esh-cmd-test/for-name-shadow-loop)
(esh-cmd-test/lisp-command-args, esh-cmd-test/subcommand)
(esh-cmd-test/subcommand-args, esh-cmd-test/subcommand-lisp): ... to
here.
(esh-cmd-test/and-operator, esh-cmd-test/or-operator)
(esh-cmd-test/for-loop-list, esh-cmd-test/for-loop-multiple-args)
(esh-cmd-test/while-loop, esh-cmd-test/until-loop)
(esh-cmd-test/if-statement, esh-cmd-test/if-else-statement)
(esh-cmd-test/unless-statement, esh-cmd-test/unless-else-statement):
New tests.
* doc/misc/eshell.texi (Invocation): Explain '&&' and '||'.
(for loop): Move from here...
(Control Flow): ... to here, and add documentation for other control
flow forms.
---
doc/misc/eshell.texi | 62 +++++++++----
lisp/eshell/em-alias.el | 2 +-
lisp/eshell/esh-cmd.el | 8 +-
lisp/eshell/esh-io.el | 50 +++++-----
test/lisp/eshell/esh-cmd-tests.el | 189 ++++++++++++++++++++++++++++++++++++++
test/lisp/eshell/eshell-tests.el | 53 -----------
6 files changed, 261 insertions(+), 103 deletions(-)
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 9f9c88582f..d643cb5096 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -201,7 +201,7 @@ history and invoking commands in a script file.
* Aliases::
* History::
* Completion::
-* for loop::
+* Control Flow::
* Scripts::
@end menu
@@ -219,12 +219,18 @@ same name; if there is no match, it then tries to execute
it as an
external command.
The semicolon (@code{;}) can be used to separate multiple command
-invocations on a single line. A command invocation followed by an
-ampersand (@code{&}) will be run in the background. Eshell has no job
-control, so you can not suspend or background the current process, or
-bring a background process into the foreground. That said, background
-processes invoked from Eshell can be controlled the same way as any
-other background process in Emacs.
+invocations on a single line. You can also separate commands with
+@code{&&} or @code{||}. When using @code{&&}, Eshell will execute the
+second command only if the first succeeds (i.e.@: has an exit
+status of 0); with @code{||}, Eshell will execute the second command
+only if the first fails.
+
+A command invocation followed by an ampersand (@code{&}) will be run
+in the background. Eshell has no job control, so you can not suspend
+or background the current process, or bring a background process into
+the foreground. That said, background processes invoked from Eshell
+can be controlled the same way as any other background process in
+Emacs.
@node Arguments
@section Arguments
@@ -1008,19 +1014,41 @@ command for which this function provides completions;
you can also name
the function @code{pcomplete/MAJOR-MODE/COMMAND} to define completions
for a specific major mode.
-@node for loop
-@section @code{for} loop
+@node Control Flow
+@section Control Flow
Because Eshell commands can not (easily) be combined with lisp forms,
-Eshell provides a command-oriented @command{for}-loop for convenience.
-The syntax is as follows:
+Eshell provides command-oriented control flow statements for
+convenience.
-@example
-@code{for VAR in TOKENS @{ command invocation(s) @}}
-@end example
+@table @code
+
+@item if @{ @var{conditional} @} @{ @var{true-commands} @}
+@itemx if @{ @var{conditional} @} @{ @var{true-commands} @} @{
@var{false-commands} @}
+Evaluate @var{true-commands} if @var{conditional} returns success
+(i.e.@: its exit code is zero); otherwise, evaluate
+@var{false-commands}.
+
+@item unless @{ @var{conditional} @} @{ @var{false-commands} @}
+@itemx unless @{ @var{conditional} @} @{ @var{false-commands} @} @{
@var{true-commands} @}
+Evaluate @var{false-commands} if @var{conditional} returns failure
+(i.e.@: its exit code is non-zero); otherwise, evaluate
+@var{true-commands}.
-where @samp{TOKENS} is a space-separated sequence of values of
-@var{VAR} for each iteration. This can even be the output of a
-command if @samp{TOKENS} is replaced with @samp{@{ command invocation @}}.
+@item while @{ @var{conditional} @} @{ @var{commands} @}
+Repeatedly evaluate @var{commands} so long as @var{conditional}
+returns success.
+
+@item until @{ @var{conditional} @} @{ @var{commands} @}
+Repeatedly evaluate @var{commands} so long as @var{conditional}
+returns failure.
+
+@item for @var{var} in @var{list}@dots{} @{ @var{commands} @}
+Iterate over each element of of @var{list}, storing the element in
+@var{var} and evaluating @var{commands}. If @var{list} is not a list,
+treat it as a list of one element. If you specify multiple
+@var{lists}, this will iterate over each of them in turn.
+
+@end table
@node Scripts
@section Scripts
diff --git a/lisp/eshell/em-alias.el b/lisp/eshell/em-alias.el
index 5d3aaf7c81..9ad218d598 100644
--- a/lisp/eshell/em-alias.el
+++ b/lisp/eshell/em-alias.el
@@ -206,7 +206,7 @@ file named by `eshell-aliases-file'.")
(let ((eshell-current-handles
(eshell-create-handles eshell-aliases-file 'overwrite)))
(eshell/alias)
- (eshell-close-handles 0))))
+ (eshell-close-handles 0 'nil))))
(defsubst eshell-lookup-alias (name)
"Check whether NAME is aliased. Return the alias if there is one."
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 775e4c1057..96272ca1a3 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -541,9 +541,7 @@ implemented via rewriting, rather than as a function."
,(eshell-invokify-arg body t)))
(setcar for-items (cadr for-items))
(setcdr for-items (cddr for-items)))
- (eshell-close-handles
- eshell-last-command-status
- (list 'quote eshell-last-command-result))))))
+ (eshell-close-handles)))))
(defun eshell-structure-basic-command (func names keyword test body
&optional else)
@@ -574,9 +572,7 @@ function."
`(let ((eshell-command-body '(nil))
(eshell-test-body '(nil)))
(,func ,test ,body ,else)
- (eshell-close-handles
- eshell-last-command-status
- (list 'quote eshell-last-command-result))))
+ (eshell-close-handles)))
(defun eshell-rewrite-while-command (terms)
"Rewrite a `while' command into its equivalent Eshell command form.
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 68e52a2c9c..27703976f6 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -254,6 +254,30 @@ a nil value of mode defaults to `insert'."
(setq idx (1+ idx))))
handles)
+(defun eshell-close-handles (&optional exit-code result handles)
+ "Close all of the current HANDLES, taking refcounts into account.
+If HANDLES is nil, use `eshell-current-handles'.
+
+EXIT-CODE is the process exit code (zero, if the command
+completed successfully). If nil, then use the exit code already
+set in `eshell-last-command-status'.
+
+RESULT is the quoted value of the last command. If nil, then use
+the value already set in `eshell-last-command-result'."
+ (when exit-code
+ (setq eshell-last-command-status exit-code))
+ (when result
+ (cl-assert (eq (car result) 'quote))
+ (setq eshell-last-command-result (cadr result)))
+ (let ((handles (or handles eshell-current-handles)))
+ (dotimes (idx eshell-number-of-handles)
+ (when-let ((handle (aref handles idx)))
+ (setcdr handle (1- (cdr handle)))
+ (when (= (cdr handle) 0)
+ (dolist (target (ensure-list (car (aref handles idx))))
+ (eshell-close-target target (= eshell-last-command-status 0)))
+ (setcar handle nil))))))
+
(defun eshell-close-target (target status)
"Close an output TARGET, passing STATUS as the result.
STATUS should be non-nil on successful termination of the output."
@@ -305,32 +329,6 @@ STATUS should be non-nil on successful termination of the
output."
((consp target)
(apply (car target) status (cdr target)))))
-(defun eshell-close-handles (exit-code &optional result handles)
- "Close all of the current handles, taking refcounts into account.
-EXIT-CODE is the process exit code; mainly, it is zero, if the command
-completed successfully. RESULT is the quoted value of the last
-command. If nil, then the meta variables for keeping track of the
-last execution result should not be changed."
- (let ((idx 0))
- (cl-assert (or (not result) (eq (car result) 'quote)))
- (setq eshell-last-command-status exit-code
- eshell-last-command-result (cadr result))
- (while (< idx eshell-number-of-handles)
- (let ((handles (or handles eshell-current-handles)))
- (when (aref handles idx)
- (setcdr (aref handles idx)
- (1- (cdr (aref handles idx))))
- (when (= (cdr (aref handles idx)) 0)
- (let ((target (car (aref handles idx))))
- (if (not (listp target))
- (eshell-close-target target (= exit-code 0))
- (while target
- (eshell-close-target (car target) (= exit-code 0))
- (setq target (cdr target)))))
- (setcar (aref handles idx) nil))))
- (setq idx (1+ idx)))
- nil))
-
(defun eshell-kill-append (string)
"Call `kill-append' with STRING, if it is indeed a string."
(if (stringp string)
diff --git a/test/lisp/eshell/esh-cmd-tests.el
b/test/lisp/eshell/esh-cmd-tests.el
new file mode 100644
index 0000000000..1d5cd29d7c
--- /dev/null
+++ b/test/lisp/eshell/esh-cmd-tests.el
@@ -0,0 +1,189 @@
+;;; esh-cmd-tests.el --- esh-cmd test suite -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for Eshell's command invocation.
+
+;;; Code:
+
+(require 'ert)
+(require 'esh-mode)
+(require 'eshell)
+
+(require 'eshell-tests-helpers
+ (expand-file-name "eshell-tests-helpers"
+ (file-name-directory (or load-file-name
+ default-directory))))
+
+(defvar eshell-test-value nil)
+
+;;; Tests:
+
+
+;; Command invocation
+
+(ert-deftest esh-cmd-test/simple-command-result ()
+ "Test invocation with a simple command."
+ (should (equal (eshell-test-command-result "+ 1 2") 3)))
+
+(ert-deftest esh-cmd-test/lisp-command ()
+ "Test invocation with an elisp command."
+ (should (equal (eshell-test-command-result "(+ 1 2)") 3)))
+
+(ert-deftest esh-cmd-test/lisp-command-with-quote ()
+ "Test invocation with an elisp command containing a quote."
+ (should (equal (eshell-test-command-result "(eq 'foo nil)") nil)))
+
+(ert-deftest esh-cmd-test/lisp-command-args ()
+ "Test invocation with elisp and trailing args.
+Test that trailing arguments outside the S-expression are
+ignored. e.g. \"(+ 1 2) 3\" => 3"
+ (should (equal (eshell-test-command-result "(+ 1 2) 3") 3)))
+
+(ert-deftest esh-cmd-test/subcommand ()
+ "Test invocation with a simple subcommand."
+ (should (equal (eshell-test-command-result "{+ 1 2}") 3)))
+
+(ert-deftest esh-cmd-test/subcommand-args ()
+ "Test invocation with a subcommand and trailing args.
+Test that trailing arguments outside the subcommand are ignored.
+e.g. \"{+ 1 2} 3\" => 3"
+ (should (equal (eshell-test-command-result "{+ 1 2} 3") 3)))
+
+(ert-deftest esh-cmd-test/subcommand-lisp ()
+ "Test invocation with an elisp subcommand and trailing args.
+Test that trailing arguments outside the subcommand are ignored.
+e.g. \"{(+ 1 2)} 3\" => 3"
+ (should (equal (eshell-test-command-result "{(+ 1 2)} 3") 3)))
+
+
+;; Logical operators
+
+(ert-deftest esh-cmd-test/and-operator ()
+ "Test logical && operator."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "[ foo = foo ] && echo hi"
+ "hi\n")
+ (eshell-command-result-p "[ foo = bar ] && echo hi"
+ "\\`\\'")))
+
+(ert-deftest esh-cmd-test/or-operator ()
+ "Test logical || operator."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "[ foo = foo ] || echo hi"
+ "\\`\\'")
+ (eshell-command-result-p "[ foo = bar ] || echo hi"
+ "hi\n")))
+
+
+;; Control flow statements
+
+(ert-deftest esh-cmd-test/for-loop ()
+ "Test invocation of a for loop."
+ (with-temp-eshell
+ (eshell-command-result-p "for i in 5 { echo $i }"
+ "5\n")))
+
+(ert-deftest esh-cmd-test/for-loop-list ()
+ "Test invocation of a for loop iterating over a list."
+ (with-temp-eshell
+ (eshell-command-result-p "for i in (list 1 2 (list 3 4)) { echo $i }"
+ "1\n2\n(3 4)\n")))
+
+(ert-deftest esh-cmd-test/for-loop-multiple-args ()
+ "Test invocation of a for loop iterating over multiple arguments."
+ (with-temp-eshell
+ (eshell-command-result-p "for i in 1 2 (list 3 4) { echo $i }"
+ "1\n2\n3\n4\n")))
+
+(ert-deftest esh-cmd-test/for-name-loop () ; bug#15231
+ "Test invocation of a for loop using `name'."
+ (let ((process-environment (cons "name" process-environment)))
+ (should (equal (eshell-test-command-result
+ "for name in 3 { echo $name }")
+ 3))))
+
+(ert-deftest esh-cmd-test/for-name-shadow-loop () ; bug#15372
+ "Test invocation of a for loop using an env-var."
+ (let ((process-environment (cons "name=env-value" process-environment)))
+ (with-temp-eshell
+ (eshell-command-result-p
+ "echo $name; for name in 3 { echo $name }; echo $name"
+ "env-value\n3\nenv-value\n"))))
+
+(ert-deftest esh-cmd-test/while-loop ()
+ "Test invocation of a while loop."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (let ((eshell-test-value 0))
+ (eshell-command-result-p
+ (concat "while {[ $eshell-test-value -ne 3 ]} "
+ "{ setq eshell-test-value (1+ eshell-test-value) }")
+ "1\n2\n3\n"))))
+
+(ert-deftest esh-cmd-test/until-loop ()
+ "Test invocation of an until loop."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (let ((eshell-test-value 0))
+ (eshell-command-result-p
+ (concat "until {[ $eshell-test-value -eq 3 ]} "
+ "{ setq eshell-test-value (1+ eshell-test-value) }")
+ "1\n2\n3\n"))))
+
+(ert-deftest esh-cmd-test/if-statement ()
+ "Test invocation of an if statement."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "if {[ foo = foo ]} {echo yes}"
+ "yes\n")
+ (eshell-command-result-p "if {[ foo = bar ]} {echo yes}"
+ "\\`\\'")))
+
+(ert-deftest esh-cmd-test/if-else-statement ()
+ "Test invocation of an if/else statement."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "if {[ foo = foo ]} {echo yes} {echo no}"
+ "yes\n")
+ (eshell-command-result-p "if {[ foo = bar ]} {echo yes} {echo no}"
+ "no\n")))
+
+(ert-deftest esh-cmd-test/unless-statement ()
+ "Test invocation of an unless statement."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "unless {[ foo = foo ]} {echo no}"
+ "\\`\\'")
+ (eshell-command-result-p "unless {[ foo = bar ]} {echo no}"
+ "no\n")))
+
+(ert-deftest esh-cmd-test/unless-else-statement ()
+ "Test invocation of an unless/else statement."
+ (skip-unless (executable-find "["))
+ (with-temp-eshell
+ (eshell-command-result-p "unless {[ foo = foo ]} {echo no} {echo yes}"
+ "yes\n")
+ (eshell-command-result-p "unless {[ foo = bar ]} {echo no} {echo yes}"
+ "no\n")))
+
+;; esh-cmd-tests.el ends here
diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el
index 5dc1877548..8423500ea7 100644
--- a/test/lisp/eshell/eshell-tests.el
+++ b/test/lisp/eshell/eshell-tests.el
@@ -36,59 +36,6 @@
;;; Tests:
-(ert-deftest eshell-test/simple-command-result ()
- "Test `eshell-command-result' with a simple command."
- (should (equal (eshell-test-command-result "+ 1 2") 3)))
-
-(ert-deftest eshell-test/lisp-command ()
- "Test `eshell-command-result' with an elisp command."
- (should (equal (eshell-test-command-result "(+ 1 2)") 3)))
-
-(ert-deftest eshell-test/lisp-command-with-quote ()
- "Test `eshell-command-result' with an elisp command containing a quote."
- (should (equal (eshell-test-command-result "(eq 'foo nil)") nil)))
-
-(ert-deftest eshell-test/for-loop ()
- "Test `eshell-command-result' with a for loop.."
- (let ((process-environment (cons "foo" process-environment)))
- (should (equal (eshell-test-command-result
- "for foo in 5 { echo $foo }") 5))))
-
-(ert-deftest eshell-test/for-name-loop () ;Bug#15231
- "Test `eshell-command-result' with a for loop using `name'."
- (let ((process-environment (cons "name" process-environment)))
- (should (equal (eshell-test-command-result
- "for name in 3 { echo $name }") 3))))
-
-(ert-deftest eshell-test/for-name-shadow-loop () ; bug#15372
- "Test `eshell-command-result' with a for loop using an env-var."
- (let ((process-environment (cons "name=env-value" process-environment)))
- (with-temp-eshell
- (eshell-command-result-p "echo $name; for name in 3 { echo $name }; echo
$name"
- "env-value\n3\nenv-value\n"))))
-
-(ert-deftest eshell-test/lisp-command-args ()
- "Test `eshell-command-result' with elisp and trailing args.
-Test that trailing arguments outside the S-expression are
-ignored. e.g. \"(+ 1 2) 3\" => 3"
- (should (equal (eshell-test-command-result "(+ 1 2) 3") 3)))
-
-(ert-deftest eshell-test/subcommand ()
- "Test `eshell-command-result' with a simple subcommand."
- (should (equal (eshell-test-command-result "{+ 1 2}") 3)))
-
-(ert-deftest eshell-test/subcommand-args ()
- "Test `eshell-command-result' with a subcommand and trailing args.
-Test that trailing arguments outside the subcommand are ignored.
-e.g. \"{+ 1 2} 3\" => 3"
- (should (equal (eshell-test-command-result "{+ 1 2} 3") 3)))
-
-(ert-deftest eshell-test/subcommand-lisp ()
- "Test `eshell-command-result' with an elisp subcommand and trailing args.
-Test that trailing arguments outside the subcommand are ignored.
-e.g. \"{(+ 1 2)} 3\" => 3"
- (should (equal (eshell-test-command-result "{(+ 1 2)} 3") 3)))
-
(ert-deftest eshell-test/pipe-headproc ()
"Check that piping a non-process to a process command waits for the process"
(skip-unless (executable-find "cat"))