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)
(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
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.
(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."
(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 }"
"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
(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