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.
This commit is contained in:
Jim Porter 2024-11-03 11:56:15 -08:00
parent ed9ea57e57
commit ee87af4f16
4 changed files with 42 additions and 5 deletions

View file

@ -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

View file

@ -280,6 +280,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:

View file

@ -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)

View file

@ -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