Fix parsing of indices in Eshell expansions
Previously, more-complex index expansions, like '$var[":" 0]' or '$var[$(expr) 0]' failed to parse correctly. * lisp/eshell/esh-var.el (Commentary): Clarify indexing and length expansions. (eshell-parse-indices): Expand docstring and support parsing inside double-quotes. (eshell-eval-indices): New function. (eshell-parse-variable): Use it. * test/lisp/eshell/esh-var-tests.el (eshell-test-value): New defvar. (esh-var-test/interp-var-indices, (esh-var-test/interp-var-split-indices) (esh-var-test/interp-var-string-split-indices) (esh-var-test/interp-var-regexp-split-indices) (esh-var-test/interp-var-assoc, esh-var-test/interp-var-length-list) (esh-var-test/interp-var-length-string) (esh-var-test/interp-var-length-alist) (esh-var-test/quoted-interp-var-indices) (esh-var-test/quoted-interp-var-split-indices) (esh-var-test/quoted-interp-var-string-split-indices) (esh-var-test/quoted-interp-var-regexp-split-indices) (esh-var-test/quoted-interp-var-assoc) (esh-var-test/quoted-interp-var-length-list) (esh-var-test/quoted-interp-var-length-string) (esh-var-test/quoted-interp-var-length-alist): New tests. * doc/misc/eshell.texi (Dollars Expansion): Expand and reword documentation for indexing and length expansions.
This commit is contained in:
parent
cccee7e840
commit
990f36fa10
3 changed files with 209 additions and 58 deletions
|
|
@ -1022,11 +1022,6 @@ Expands to the value bound to @var{var}. This is useful to
|
|||
disambiguate the variable name when concatenating it with another
|
||||
value, such as @samp{$"@var{var}"-suffix}.
|
||||
|
||||
@item $#@var{var}
|
||||
Expands to the length of the value bound to @var{var}. Raises an error
|
||||
if the value is not a sequence
|
||||
(@pxref{Sequences Arrays Vectors, Sequences, , elisp, The Emacs Lisp Reference Manual}).
|
||||
|
||||
@item $(@var{lisp})
|
||||
Expands to the result of evaluating the S-expression @code{(@var{lisp})}. On
|
||||
its own, this is identical to just @code{(@var{lisp})}, but with the @code{$},
|
||||
|
|
@ -1041,34 +1036,35 @@ As with @samp{$@{@var{command}@}}, evaluates the Eshell command invocation
|
|||
@command{@var{command}}, but writes the output to a temporary file and
|
||||
returns the file name.
|
||||
|
||||
@item $@var{var}[i]
|
||||
Expands to the @code{i}th element of the value bound to @var{var}. If
|
||||
the value is a string, it will be split at whitespace to make it a list.
|
||||
Again, raises an error if the value is not a sequence.
|
||||
@item $@var{expr}[@var{i...}]
|
||||
Expands to the @var{i}th element of the result of @var{expr}, an
|
||||
expression in one of the above forms listed here. If multiple indices
|
||||
are supplied, this will return a list containing the elements for each
|
||||
index. If @var{expr}'s value is a string, it will first be split at
|
||||
whitespace to make it a list. If @var{expr}'s value is an alist
|
||||
(@pxref{Association List Type, Association Lists, , elisp, The Emacs
|
||||
Lisp Reference Manual}), this will call @code{assoc} on the result of
|
||||
@var{expr}, returning the @code{cdr} of the element of the result
|
||||
whose car is equal to @code{"i"}. Raises an error if the value is not
|
||||
a sequence (@pxref{Sequences Arrays Vectors, Sequences, , elisp, The
|
||||
Emacs Lisp Reference Manual}).
|
||||
|
||||
@item $@var{var}[: i]
|
||||
As above, but now splitting occurs at the colon character.
|
||||
Multiple sets of indices can also be specified. For example, if
|
||||
@var{var} is a list of lists, @samp{$@var{var}[0][0]} is equivalent to
|
||||
@samp{(caar @var{var})}.
|
||||
|
||||
@item $@var{var}[: i j]
|
||||
As above, but instead of returning just a string, it now returns a list
|
||||
of two strings. If the result is being interpolated into a larger
|
||||
string, this list will be flattened into one big string, with each
|
||||
element separated by a space.
|
||||
@item $@var{expr}[@var{regexp} @var{i...}]
|
||||
As above (when @var{expr} expands to a string), but use @var{regexp}
|
||||
to split the string. @var{regexp} can be any form other than a number
|
||||
or a plain variable name. For example, @samp{$@var{var}[: 0]} will
|
||||
return the first element of a colon-delimited string.
|
||||
|
||||
@item $@var{var}["\\\\" i]
|
||||
Separate on backslash characters. Actually, the first argument -- if it
|
||||
doesn't have the form of a number, or a plain variable name -- can be
|
||||
any regular expression. So to split on numbers, use
|
||||
@samp{$@var{var}["[0-9]+" 10 20]}.
|
||||
|
||||
@item $@var{var}[hello]
|
||||
Calls @code{assoc} on @var{var} with @code{"hello"}, expecting it to be
|
||||
an alist (@pxref{Association List Type, Association Lists, , elisp,
|
||||
The Emacs Lisp Reference Manual}).
|
||||
|
||||
@item $#@var{var}[hello]
|
||||
Returns the length of the @code{cdr} of the element of @var{var} whose
|
||||
car is equal to @code{"hello"}.
|
||||
@item $#@var{expr}
|
||||
Expands to the length of the result of @var{expr}, an expression in
|
||||
one of the above forms. For example, @samp{$#@var{var}} returns the
|
||||
length of the variable @var{var} and @samp{$#@var{var}[0]} returns the
|
||||
length of the first element of @var{var}. Again, raises an error if
|
||||
the result of @var{expr} is not a sequence.
|
||||
|
||||
@end table
|
||||
|
||||
|
|
|
|||
|
|
@ -39,11 +39,6 @@
|
|||
;;
|
||||
;; Only "MYVAR" is part of the variable name in this case.
|
||||
;;
|
||||
;; $#VARIABLE
|
||||
;;
|
||||
;; Returns the length of the value of VARIABLE. This could also be
|
||||
;; done using the `length' Lisp function.
|
||||
;;
|
||||
;; $(lisp)
|
||||
;;
|
||||
;; Returns result of Lisp evaluation. Note: Used alone like this, it
|
||||
|
|
@ -61,38 +56,36 @@
|
|||
;; Evaluates an eshell subcommand, redirecting the output to a
|
||||
;; temporary file, and returning the file name.
|
||||
;;
|
||||
;; $ANYVAR[10]
|
||||
;; $EXPR[10]
|
||||
;;
|
||||
;; Return the 10th element of ANYVAR. If ANYVAR's value is a string,
|
||||
;; it will be split in order to make it a list. The splitting will
|
||||
;; occur at whitespace.
|
||||
;; Return the 10th element of $EXPR, which can be any dollar
|
||||
;; expression. If $EXPR's value is a string, it will be split in
|
||||
;; order to make it a list. The splitting will occur at whitespace.
|
||||
;;
|
||||
;; $ANYVAR[: 10]
|
||||
;; $EXPR[10 20]
|
||||
;;
|
||||
;; As above, except that splitting occurs at the colon now.
|
||||
;; As above, but instead of returning a single element, it now returns a
|
||||
;; list of two elements.
|
||||
;;
|
||||
;; $ANYVAR[: 10 20]
|
||||
;; $EXPR[: 10]
|
||||
;;
|
||||
;; As above, but instead of returning just a string, it now returns a
|
||||
;; list of two strings. If the result is being interpolated into a
|
||||
;; larger string, this list will be flattened into one big string,
|
||||
;; with each element separated by a space.
|
||||
;; Like $EXPR[10], except that splitting occurs at the colon now.
|
||||
;;
|
||||
;; $ANYVAR["\\\\" 10]
|
||||
;; $EXPR["\\\\" 10]
|
||||
;;
|
||||
;; Separate on backslash characters. Actually, the first argument --
|
||||
;; if it doesn't have the form of a number, or a plain variable name
|
||||
;; -- can be any regular expression. So to split on numbers, use
|
||||
;; '$ANYVAR["[0-9]+" 10 20]'.
|
||||
;; '$EXPR["[0-9]+" 10 20]'.
|
||||
;;
|
||||
;; $ANYVAR[hello]
|
||||
;; $EXPR[hello]
|
||||
;;
|
||||
;; Calls `assoc' on ANYVAR with 'hello', expecting it to be an alist.
|
||||
;; Calls `assoc' on $EXPR with 'hello', expecting it to be an alist.
|
||||
;;
|
||||
;; $#ANYVAR[hello]
|
||||
;; $#EXPR
|
||||
;;
|
||||
;; Returns the length of the cdr of the element of ANYVAR who car is
|
||||
;; equal to "hello".
|
||||
;; Returns the length of the value of $EXPR. This could also be
|
||||
;; done using the `length' Lisp function.
|
||||
;;
|
||||
;; There are also a few special variables defined by Eshell. '$$' is
|
||||
;; the value of the last command (t or nil, in the case of an external
|
||||
|
|
@ -416,7 +409,7 @@ process any indices that come after the variable reference."
|
|||
(eshell-parse-indices))
|
||||
;; This is an expression that will be evaluated by `eshell-do-eval',
|
||||
;; which only support let-binding of dynamically-scoped vars
|
||||
value `(let ((indices ',indices)) ,value))
|
||||
value `(let ((indices (eshell-eval-indices ',indices))) ,value))
|
||||
(if get-len
|
||||
`(length ,value)
|
||||
value)))
|
||||
|
|
@ -504,19 +497,29 @@ Possible options are:
|
|||
(defvar eshell-glob-function)
|
||||
|
||||
(defun eshell-parse-indices ()
|
||||
"Parse and return a list of list of indices."
|
||||
"Parse and return a list of index-lists.
|
||||
|
||||
For example, \"[0 1][2]\" becomes:
|
||||
((\"0\" \"1\") (\"2\")."
|
||||
(let (indices)
|
||||
(while (eq (char-after) ?\[)
|
||||
(let ((end (eshell-find-delimiter ?\[ ?\])))
|
||||
(if (not end)
|
||||
(throw 'eshell-incomplete ?\[)
|
||||
(forward-char)
|
||||
(let (eshell-glob-function)
|
||||
(setq indices (cons (eshell-parse-arguments (point) end)
|
||||
indices)))
|
||||
(eshell-with-temp-command (or (eshell-parse-inner-double-quote end)
|
||||
(cons (point) end))
|
||||
(let (eshell-glob-function (eshell-current-quoted nil))
|
||||
(setq indices (cons (eshell-parse-arguments
|
||||
(point-min) (point-max))
|
||||
indices))))
|
||||
(goto-char (1+ end)))))
|
||||
(nreverse indices)))
|
||||
|
||||
(defun eshell-eval-indices (indices)
|
||||
"Evaluate INDICES, a list of index-lists generated by `eshell-parse-indices'."
|
||||
(mapcar (lambda (i) (mapcar #'eval i)) indices))
|
||||
|
||||
(defun eshell-get-variable (name &optional indices)
|
||||
"Get the value for the variable NAME."
|
||||
(let* ((alias (assoc name eshell-variable-aliases-list))
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
(file-name-directory (or load-file-name
|
||||
default-directory))))
|
||||
|
||||
(defvar eshell-test-value nil)
|
||||
|
||||
;;; Tests:
|
||||
|
||||
|
||||
|
|
@ -56,6 +58,76 @@
|
|||
(should (equal (eshell-test-command-result "echo $\"user-login-name\"-foo")
|
||||
(concat user-login-name "-foo"))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-indices ()
|
||||
"Interpolate list variable with indices"
|
||||
(let ((eshell-test-value '("zero" "one" "two" "three" "four")))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[0]")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[0 2]")
|
||||
'("zero" "two")))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[0 2 4]")
|
||||
'("zero" "two" "four")))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-split-indices ()
|
||||
"Interpolate string variable with indices"
|
||||
(let ((eshell-test-value "zero one two three four"))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[0]")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[0 2]")
|
||||
'("zero" "two")))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[0 2 4]")
|
||||
'("zero" "two" "four")))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-string-split-indices ()
|
||||
"Interpolate string variable with string splitter and indices"
|
||||
(let ((eshell-test-value "zero:one:two:three:four"))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[: 0]")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result "echo $eshell-test-value[: 0 2]")
|
||||
'("zero" "two")))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-regexp-split-indices ()
|
||||
"Interpolate string variable with regexp splitter and indices"
|
||||
(let ((eshell-test-value "zero:one!two:three!four"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo $eshell-test-value['[:!]' 0]")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo $eshell-test-value['[:!]' 0 2]")
|
||||
'("zero" "two")))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo $eshell-test-value[\"[:!]\" 0]")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo $eshell-test-value[\"[:!]\" 0 2]")
|
||||
'("zero" "two")))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-assoc ()
|
||||
"Interpolate alist variable with index"
|
||||
(let ((eshell-test-value '(("foo" . 1))))
|
||||
(should (eq (eshell-test-command-result "echo $eshell-test-value[foo]")
|
||||
1))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-length-list ()
|
||||
"Interpolate length of list variable"
|
||||
(let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9)))))
|
||||
(should (eq (eshell-test-command-result "echo $#eshell-test-value") 3))
|
||||
(should (eq (eshell-test-command-result "echo $#eshell-test-value[1]") 1))
|
||||
(should (eq (eshell-test-command-result "echo $#eshell-test-value[2][1]")
|
||||
4))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-length-string ()
|
||||
"Interpolate length of string variable"
|
||||
(let ((eshell-test-value "foobar"))
|
||||
(should (eq (eshell-test-command-result "echo $#eshell-test-value") 6))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-var-length-alist ()
|
||||
"Interpolate length of alist variable"
|
||||
(let ((eshell-test-value '(("foo" . (1 2 3)))))
|
||||
(should (eq (eshell-test-command-result "echo $#eshell-test-value") 1))
|
||||
(should (eq (eshell-test-command-result "echo $#eshell-test-value[foo]")
|
||||
3))))
|
||||
|
||||
(ert-deftest esh-var-test/interp-lisp ()
|
||||
"Interpolate Lisp form evaluation"
|
||||
(should (equal (eshell-test-command-result "+ $(+ 1 2) 3") 6)))
|
||||
|
|
@ -112,6 +184,86 @@
|
|||
"echo \"hi, $\\\"user-login-name\\\"\"")
|
||||
(concat "hi, " user-login-name))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-indices ()
|
||||
"Interpolate string variable with indices inside double-quotes"
|
||||
(let ((eshell-test-value '("zero" "one" "two" "three" "four")))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[0]\"")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[0 2]\"")
|
||||
'("zero" "two")))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[0 2 4]\"")
|
||||
'("zero" "two" "four")))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-split-indices ()
|
||||
"Interpolate string variable with indices inside double-quotes"
|
||||
(let ((eshell-test-value "zero one two three four"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[0]\"")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[0 2]\"")
|
||||
'("zero" "two")))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-string-split-indices ()
|
||||
"Interpolate string variable with string splitter and indices
|
||||
inside double-quotes"
|
||||
(let ((eshell-test-value "zero:one:two:three:four"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[: 0]\"")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[: 0 2]\"")
|
||||
'("zero" "two")))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-regexp-split-indices ()
|
||||
"Interpolate string variable with regexp splitter and indices"
|
||||
(let ((eshell-test-value "zero:one!two:three!four"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value['[:!]' 0]\"")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value['[:!]' 0 2]\"")
|
||||
'("zero" "two")))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[\\\"[:!]\\\" 0]\"")
|
||||
"zero"))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[\\\"[:!]\\\" 0 2]\"")
|
||||
'("zero" "two")))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-assoc ()
|
||||
"Interpolate alist variable with index inside double-quotes"
|
||||
(let ((eshell-test-value '(("foo" . 1))))
|
||||
(should (equal (eshell-test-command-result
|
||||
"echo \"$eshell-test-value[foo]\"")
|
||||
1))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-length-list ()
|
||||
"Interpolate length of list variable inside double-quotes"
|
||||
(let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9)))))
|
||||
(should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"") 3))
|
||||
(should (eq (eshell-test-command-result "echo \"$#eshell-test-value[1]\"")
|
||||
1))
|
||||
(should (eq (eshell-test-command-result
|
||||
"echo \"$#eshell-test-value[2][1]\"")
|
||||
4))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-length-string ()
|
||||
"Interpolate length of string variable inside double-quotes"
|
||||
(let ((eshell-test-value "foobar"))
|
||||
(should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"")
|
||||
6))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-var-length-alist ()
|
||||
"Interpolate length of alist variable inside double-quotes"
|
||||
(let ((eshell-test-value '(("foo" . (1 2 3)))))
|
||||
(should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"") 1))
|
||||
(should (eq (eshell-test-command-result "echo \"$#eshell-test-value[foo]\"")
|
||||
3))))
|
||||
|
||||
(ert-deftest esh-var-test/quoted-interp-lisp ()
|
||||
"Interpolate Lisp form evaluation inside double-quotes"
|
||||
(should (equal (eshell-test-command-result
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue