From f9e7d25cf682e4f0ff113f7fa8b564c2481cf32d Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Sun, 3 Nov 2024 11:56:15 -0800 Subject: [PATCH] Add support for range objects in Eshell "for" loops * lisp/eshell/esh-cmd.el (eshell-for-iterate): Add support for 'eshell-range' objects. * test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test/for-loop-range): New test. * doc/misc/eshell.texi (Control Flow): Update documentation. * etc/NEWS: Announce this change. (cherry picked from commit ee87af4f1603d2042afa641e74df0403a49af1c5) --- doc/misc/eshell.texi | 14 +++++++++----- etc/NEWS | 6 ++++++ lisp/eshell/esh-cmd.el | 12 ++++++++++++ test/lisp/eshell/esh-cmd-tests.el | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 5470d519687..146fed6c8f8 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1761,11 +1761,15 @@ satisfied. Repeatedly evaluate @var{subcommand} until @var{conditional} is satisfied. -@item for @var{var} in @var{list}@dots{} @var{subcommand} -Iterate over each element of @var{list}, storing the element in -@var{var} and evaluating @var{subcommand}. 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. +@item for @var{var} in @var{sequence}@dots{} @var{subcommand} +Iterate over each element of @var{sequence}, storing the element in +@var{var} and evaluating @var{subcommand}. If @var{sequence} is a +range of the form @code{@var{begin}..@var{end}}, iterate over each +integer between @var{begin} and @var{end}, not including @var{end}. If +@var{sequence} is not a sequence, treat it as a list of one element. + +If you specify multiple @var{sequences}, this will iterate over each of +them in turn. @end table diff --git a/etc/NEWS b/etc/NEWS index d841bfd2135..af23b71d0cc 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -226,6 +226,12 @@ These functions now take an optional ERROR-TARGET argument to control where to send the standard error output. See the "(eshell) Entry Points" node in the Eshell manual for more details. ++++ +*** You can now loop over ranges of integers with the Eshell 'for' command. +When passing a range like 'BEGIN..END' to the Eshell 'for' command, +Eshell will now iterate over each integer between BEGIN and END, not +including END. + +++ *** Conditional statements in Eshell now use an 'else' keyword. Eshell now prefers the following form when writing conditionals: diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 441a39ac86a..4b7061c0e01 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -530,7 +530,19 @@ the second is ignored." "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) + (when (eshell--range-string-p arg) + (setq arg (eshell--string-to-range arg))) (cond + ((eshell-range-p arg) + (let ((i (eshell-range-begin arg)) + (end (eshell-range-end arg))) + ;; NOTE: We could support unbounded ranges here, but those + ;; aren't very easy to use in Eshell yet. (We'd need something + ;; like the "break" statement for "for" loops.) + (cl-assert (and i end)) + (while (< i end) + (iter-yield i) + (cl-incf i)))) ((stringp arg) (iter-yield arg)) ((listp arg) diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el index ae1aed59b45..64ac3a8c2f6 100644 --- a/test/lisp/eshell/esh-cmd-tests.el +++ b/test/lisp/eshell/esh-cmd-tests.el @@ -341,6 +341,21 @@ processes correctly." (eshell-match-command-output "for i in `[1 2 3] { echo $i }" "1\n2\n3\n"))) +(ert-deftest esh-cmd-test/for-loop-range () + "Test invocation of a for loop iterating over a range." + (with-temp-eshell + (eshell-match-command-output "for i in 1..5 { echo $i }" + "1\n2\n3\n4\n") + (let ((eshell-test-value 2)) + (eshell-match-command-output "for i in $eshell-test-value..5 { echo $i }" + "2\n3\n4\n")) + ;; Make sure range syntax only work when it's part of the literal + ;; syntax; a variable expanding to something that looks like a range + ;; doesn't count. + (let ((eshell-test-value "1..5")) + (eshell-match-command-output "for i in $eshell-test-value { echo $i }" + "1..5\n"))))) + (ert-deftest esh-cmd-test/for-loop-mixed-args () "Test invocation of a for loop iterating over multiple arguments." (with-temp-eshell -- 2.39.5