From 179cc3d7b9ddfb2f1888935bf8d7f08706b75273 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Sun, 3 Nov 2024 11:22:27 -0800 Subject: [PATCH] Improve handling of Eshell "for" loops 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 | 35 +++++++++++++++++------------- test/lisp/eshell/esh-cmd-tests.el | 26 +++++++++++++--------- test/lisp/eshell/esh-proc-tests.el | 23 +++++++++++--------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 6541430014a..88d9c0affef 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -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. diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el index 8b68013c60d..ae1aed59b45 100644 --- a/test/lisp/eshell/esh-cmd-tests.el +++ b/test/lisp/eshell/esh-cmd-tests.el @@ -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 diff --git a/test/lisp/eshell/esh-proc-tests.el b/test/lisp/eshell/esh-proc-tests.el index 3121e751006..973d9ccc213 100644 --- a/test/lisp/eshell/esh-proc-tests.el +++ b/test/lisp/eshell/esh-proc-tests.el @@ -135,16 +135,19 @@ (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")))))) ;; Pipelines -- 2.39.5