emacs-diffs
[Top][All Lists]
Advanced

[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"))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]