]> git.eshelyaron.com Git - emacs.git/commitdiff
Improve handling of Eshell "for" loops
authorJim Porter <jporterbugs@gmail.com>
Sun, 3 Nov 2024 19:22:27 +0000 (11:22 -0800)
committerEshel Yaron <me@eshelyaron.com>
Fri, 8 Nov 2024 13:30:00 +0000 (14:30 +0100)
This fixes some errors with more-complex string forms, and also allows
iterating over sequences other than just lists.

* lisp/eshell/esh-cmd.el (eshell-for-iterate): New function...
(eshell-rewrite-for-command): ... use it.

* test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test/for-loop): Test
multiple values.
(esh-cmd-test/for-loop-string, esh-cmd-test/for-loop-vector): New tests.
(esh-cmd-test/for-loop-mixed-args): Rename.

* test/lisp/eshell/esh-proc-tests.el
(esh-proc-test/sentinel/change-buffer): Make sure all the processes get
cleaned up.

(cherry picked from commit 08d5994b435c119bde8b8ac8f7152810d1814d1e)

lisp/eshell/esh-cmd.el
test/lisp/eshell/esh-cmd-tests.el
test/lisp/eshell/esh-proc-tests.el

index 6541430014a1cf95eec3b557796cd87e35d47474..88d9c0affef45c4881e0103113c54b37a3f79c86 100644 (file)
@@ -526,6 +526,20 @@ the second is ignored."
 (defvar eshell--local-vars nil
   "List of locally bound vars that should take precedence over env-vars.")
 
+(iter-defun eshell-for-iterate (&rest args)
+  "Iterate over the elements of each sequence in ARGS.
+If ARGS is not a sequence, treat it as a list of one element."
+  (dolist (arg args)
+    (cond
+     ((stringp arg)
+      (iter-yield arg))
+     ((listp arg)
+      (dolist (i arg) (iter-yield i)))
+     ((arrayp arg)
+      (dotimes (i (length arg)) (iter-yield (aref arg i))))
+     (t
+      (iter-yield arg)))))
+
 (defun eshell-rewrite-for-command (terms)
   "Rewrite a `for' command into its equivalent Eshell command form.
 Because the implementation of `for' relies upon conditional evaluation
@@ -533,23 +547,14 @@ of its argument (i.e., use of a Lisp special form), it must be
 implemented via rewriting, rather than as a function."
   (if (and (equal (car terms) "for")
           (equal (nth 2 terms) "in"))
-      (let ((for-items (make-symbol "for-items"))
+      (let ((iter-symbol (intern (nth 1 terms)))
             (body (car (last terms))))
        (setcdr (last terms 2) nil)
-        `(let ((,for-items
-                (append
-                 ,@(mapcar
-                    (lambda (elem)
-                      (if (listp elem)
-                          (eshell-term-as-value elem)
-                        `(list ,elem)))
-                    (nthcdr 3 terms)))))
-           (while ,for-items
-             (let ((,(intern (cadr terms)) (car ,for-items))
-                  (eshell--local-vars (cons ',(intern (cadr terms))
-                                             eshell--local-vars)))
-              ,body)
-             (setq ,for-items (cdr ,for-items)))))))
+        `(let ((eshell--local-vars (cons ',iter-symbol eshell--local-vars)))
+           (iter-do (,iter-symbol (eshell-for-iterate
+                                   ,@(mapcar #'eshell-term-as-value
+                                             (nthcdr 3 terms))))
+             ,body)))))
 
 (defun eshell-structure-basic-command (func names keyword test &rest body)
   "With TERMS, KEYWORD, and two NAMES, structure a basic command.
index 8b68013c60d84463e1962f0d40ca28d40524b36c..ae1aed59b45ec0c9e15f96736404d4b290c2c254 100644 (file)
@@ -319,8 +319,15 @@ processes correctly."
 (ert-deftest esh-cmd-test/for-loop ()
   "Test invocation of a for loop."
   (with-temp-eshell
-   (eshell-match-command-output "for i in 5 { echo $i }"
-                                "5\n")))
+    (eshell-match-command-output "for i in 1 2 { echo $i }"
+                                 "1\n2\n")))
+
+(ert-deftest esh-cmd-test/for-loop-string ()
+  "Test invocation of a for loop with complex string arguments."
+  (let ((eshell-test-value "X"))
+    (with-temp-eshell
+      (eshell-match-command-output "for i in a b$eshell-test-value { echo $i }"
+                                   "a\nbX\n"))))
 
 (ert-deftest esh-cmd-test/for-loop-list ()
   "Test invocation of a for loop iterating over a list."
@@ -328,7 +335,13 @@ processes correctly."
    (eshell-match-command-output "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 ()
+(ert-deftest esh-cmd-test/for-loop-vector ()
+  "Test invocation of a for loop iterating over a vector."
+  (with-temp-eshell
+    (eshell-match-command-output "for i in `[1 2 3] { echo $i }"
+                                 "1\n2\n3\n")))
+
+(ert-deftest esh-cmd-test/for-loop-mixed-args ()
   "Test invocation of a for loop iterating over multiple arguments."
   (with-temp-eshell
    (eshell-match-command-output "for i in 1 2 (list 3 4) { echo $i }"
@@ -348,13 +361,6 @@ processes correctly."
       "echo $name; for name in 3 { echo $name }; echo $name"
       "env-value\n3\nenv-value\n"))))
 
-(ert-deftest esh-cmd-test/for-loop-for-items-shadow ()
-  "Test that the variable `for-items' isn't shadowed inside for loops."
-  (with-temp-eshell
-   (with-no-warnings (setq-local for-items "hello"))
-   (eshell-match-command-output "for i in 1 { echo $for-items }"
-                                "hello\n")))
-
 (ert-deftest esh-cmd-test/for-loop-lisp-body ()
   "Test invocation of a for loop with a Lisp body form."
   (with-temp-eshell
index 3121e751006e2bf301093589c14da2614c6dc56b..973d9ccc213177c5c2eb167b89c0523e7e03ab8b 100644 (file)
 (ert-deftest esh-proc-test/sentinel/change-buffer ()
   "Check that changing the current buffer while running a command works.
 See bug#71778."
-  (eshell-with-temp-buffer bufname ""
-    (with-temp-eshell
-      (let (eshell-test-value)
-        (eshell-insert-command
-         (concat (format "for i in 1 2 {sleep 1; echo hello} > #<%s>; " bufname)
-                 "setq eshell-test-value t"))
-        (with-current-buffer bufname
-          (eshell-wait-for (lambda () eshell-test-value))
-          (should (equal (buffer-string) "hellohello")))
-        (eshell-match-command-output "echo goodbye" "\\`goodbye\n")))))
+  (let ((starting-process-list (process-list)))
+    (eshell-with-temp-buffer bufname ""
+      (with-temp-eshell
+        (let (eshell-test-value)
+          (eshell-insert-command
+           (concat (format "for i in 1 2 {sleep 1; echo hello} > #<%s>; "
+                           bufname)
+                   "setq eshell-test-value t"))
+          (with-current-buffer bufname
+            (eshell-wait-for (lambda () eshell-test-value))
+            (should (equal (buffer-string) "hellohello")))
+          (should (equal (process-list) starting-process-list))
+          (eshell-match-command-output "echo goodbye" "\\`goodbye\n"))))))
 
 \f
 ;; Pipelines