summaryrefslogtreecommitdiff
path: root/lisp/eshell
diff options
context:
space:
mode:
authorJim Porter <jporterbugs@gmail.com>2022-07-09 16:26:55 -0700
committerJim Porter <jporterbugs@gmail.com>2022-09-04 15:15:01 -0700
commitab7e94fb1d9b794c9d199435d72f569fba6ab017 (patch)
treebdb8f5d264c9377c519ccc61009a4d9ab9551be0 /lisp/eshell
parent3d6c013a27e0b72c8fbe2d47f752dd0dfd4ff47a (diff)
downloademacs-ab7e94fb1d9b794c9d199435d72f569fba6ab017.tar.gz
emacs-ab7e94fb1d9b794c9d199435d72f569fba6ab017.tar.bz2
emacs-ab7e94fb1d9b794c9d199435d72f569fba6ab017.zip
Add support for more kinds of redirect operators in Eshell
* lisp/eshell/esh-arg.el: Require cl-lib. (eshell-finish-arg): Allow passing multiple ARGUMENTS. (eshell-quote-argument): Handle the case when 'eshell-finish-arg' was passed multiple arguments. * lisp/eshell/esh-cmd.el (eshell-do-pipelines) (eshell-do-pipelines-synchronously): Only set stdout output handle. * lisp/eshell/esh-io.el (eshell-redirection-operators-alist): New constant. (eshell-io-initialize): Prefer sharp quotes for functions. (eshell-parse-redirection, eshell-strip-redirections): Add support for more redirection forms. (eshell-copy-output-handle, eshell-set-all-output-handles): New functions. * test/lisp/eshell/esh-io-tests.el (esh-io-test/redirect-all/overwrite, esh-io-test/redirect-all/append) (esh-io-test/redirect-all/insert, esh-io-test/redirect-copy) (esh-io-test/redirect-copy-first, esh-io-test/redirect-pipe): New tests. * doc/misc/eshell.texi (Redirection): Document new redirection syntax. (Pipelines): Document '|&' syntax. (Bugs and ideas): Update item about redirection syntax. * etc/NEWS: Announce this change.
Diffstat (limited to 'lisp/eshell')
-rw-r--r--lisp/eshell/esh-arg.el23
-rw-r--r--lisp/eshell/esh-cmd.el4
-rw-r--r--lisp/eshell/esh-io.el141
3 files changed, 126 insertions, 42 deletions
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index 50fb7f5fdc6..576d32b8c5d 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -29,6 +29,9 @@
(require 'esh-util)
+(eval-when-compile
+ (require 'cl-lib))
+
(defgroup eshell-arg nil
"Argument parsing involves transforming the arguments passed on the
command line into equivalent Lisp forms that, when evaluated, will
@@ -248,10 +251,16 @@ convert the result to a number as well."
eshell-current-modifiers (cdr eshell-current-modifiers))))
(setq eshell-current-modifiers nil))
-(defun eshell-finish-arg (&optional argument)
- "Finish the current ARGUMENT being processed."
- (if argument
- (setq eshell-current-argument argument))
+(defun eshell-finish-arg (&rest arguments)
+ "Finish the current argument being processed.
+If any ARGUMENTS are specified, they will be added to the final
+argument list in place of the value of the current argument."
+ (when arguments
+ (if (= (length arguments) 1)
+ (setq eshell-current-argument (car arguments))
+ (cl-assert (and (not eshell-arg-listified)
+ (not eshell-current-modifiers)))
+ (setq eshell-current-argument (cons 'eshell-flatten-args arguments))))
(throw 'eshell-arg-done t))
(defun eshell-quote-argument (string)
@@ -291,7 +300,11 @@ Point is left at the end of the arguments."
(if (= (point) here)
(error "Failed to parse argument `%s'"
(buffer-substring here (point-max))))
- (and arg (nconc args (list arg)))))))
+ (when arg
+ (nconc args
+ (if (eq (car-safe arg) 'eshell-flatten-args)
+ (cdr arg)
+ (list arg))))))))
(throw 'eshell-incomplete (if (listp delim)
delim
(list delim (point) (cdr args)))))
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index a43ad77213d..413336e3eb5 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -810,8 +810,6 @@ This macro calls itself recursively, with NOTFIRST non-nil."
`(let ((nextproc
(eshell-do-pipelines (quote ,(cdr pipeline)) t)))
(eshell-set-output-handle ,eshell-output-handle
- 'append nextproc)
- (eshell-set-output-handle ,eshell-error-handle
'append nextproc)))
,(let ((head (car pipeline)))
(if (memq (car head) '(let progn))
@@ -842,8 +840,6 @@ This is used on systems where async subprocesses are not supported."
,(when (cdr pipeline)
`(let ((output-marker ,(point-marker)))
(eshell-set-output-handle ,eshell-output-handle
- 'append output-marker)
- (eshell-set-output-handle ,eshell-error-handle
'append output-marker)))
,(let ((head (car pipeline)))
(if (memq (car head) '(let progn))
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 01e8aceeabd..4620565f857 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -154,6 +154,14 @@ not be added to this variable."
;;; Internal Variables:
+(defconst eshell-redirection-operators-alist
+ '(("<" . input) ; FIXME: Not supported yet.
+ (">" . overwrite)
+ (">>" . append)
+ (">>>" . insert))
+ "An association list of redirection operators to symbols
+describing the mode, e.g. for using with `eshell-get-target'.")
+
(defvar eshell-current-handles nil)
(defvar eshell-last-command-status 0
@@ -173,53 +181,104 @@ not be added to this variable."
(defun eshell-io-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the I/O subsystem code."
(add-hook 'eshell-parse-argument-hook
- 'eshell-parse-redirection nil t)
+ #'eshell-parse-redirection nil t)
(make-local-variable 'eshell-current-redirections)
(add-hook 'eshell-pre-rewrite-command-hook
- 'eshell-strip-redirections nil t)
+ #'eshell-strip-redirections nil t)
(add-function :filter-return (local 'eshell-post-rewrite-command-function)
#'eshell--apply-redirections))
(defun eshell-parse-redirection ()
- "Parse an output redirection, such as `2>'."
- (if (and (not eshell-current-quoted)
- (looking-at "\\([0-9]\\)?\\(<\\|>+\\)&?\\([0-9]\\)?\\s-*"))
+ "Parse an output redirection, such as `2>' or `>&'."
+ (when (not eshell-current-quoted)
+ (cond
+ ;; Copying a handle (e.g. `2>&1').
+ ((looking-at (rx (? (group digit))
+ (group (or "<" ">"))
+ "&" (group digit)
+ (* (syntax whitespace))))
+ (let ((source (string-to-number (or (match-string 1) "1")))
+ (mode (cdr (assoc (match-string 2)
+ eshell-redirection-operators-alist)))
+ (target (string-to-number (match-string 3))))
+ (when (eq mode 'input)
+ (error "Eshell does not support input redirection"))
+ (goto-char (match-end 0))
+ (eshell-finish-arg (list 'eshell-copy-output-handle
+ source target))))
+ ;; Shorthand for redirecting both stdout and stderr (e.g. `&>').
+ ((looking-at (rx (or (seq (group (** 1 3 ">")) "&")
+ (seq "&" (group-n 1 (** 1 3 ">"))))
+ (* (syntax whitespace))))
+ (if eshell-current-argument
+ (eshell-finish-arg)
+ (goto-char (match-end 0))
+ (eshell-finish-arg
+ (let ((mode (cdr (assoc (match-string 1)
+ eshell-redirection-operators-alist))))
+ (list 'eshell-set-all-output-handles
+ (list 'quote mode))))))
+ ;; Shorthand for piping both stdout and stderr (i.e. `|&').
+ ((looking-at (rx "|&" (* (syntax whitespace))))
+ (if eshell-current-argument
+ (eshell-finish-arg)
+ (goto-char (match-end 0))
+ (eshell-finish-arg
+ '(eshell-copy-output-handle eshell-error-handle
+ eshell-output-handle)
+ '(eshell-operator "|"))))
+ ;; Regular redirecting (e.g. `2>').
+ ((looking-at (rx (? (group digit))
+ (group (or "<" (** 1 3 ">")))
+ (* (syntax whitespace))))
(if eshell-current-argument
- (eshell-finish-arg)
- (let ((sh (match-string 1))
- (oper (match-string 2))
-; (th (match-string 3))
- )
- (if (string= oper "<")
- (error "Eshell does not support input redirection"))
- (eshell-finish-arg
- (prog1
- (list 'eshell-set-output-handle
- (or (and sh (string-to-number sh)) 1)
- (list 'quote
- (aref [overwrite append insert]
- (1- (length oper)))))
- (goto-char (match-end 0))))))))
+ (eshell-finish-arg)
+ (let ((source (if (match-string 1)
+ (string-to-number (match-string 1))
+ eshell-output-handle))
+ (mode (cdr (assoc (match-string 2)
+ eshell-redirection-operators-alist))))
+ (when (eq mode 'input)
+ (error "Eshell does not support input redirection"))
+ (goto-char (match-end 0))
+ (eshell-finish-arg
+ ;; Note: the target will be set later by
+ ;; `eshell-strip-redirections'.
+ (list 'eshell-set-output-handle
+ source (list 'quote mode)))))))))
(defun eshell-strip-redirections (terms)
"Rewrite any output redirections in TERMS."
(setq eshell-current-redirections (list t))
(let ((tl terms)
- (tt (cdr terms)))
+ (tt (cdr terms)))
(while tt
- (if (not (and (consp (car tt))
- (eq (caar tt) 'eshell-set-output-handle)))
- (setq tt (cdr tt)
- tl (cdr tl))
- (unless (cdr tt)
- (error "Missing redirection target"))
- (nconc eshell-current-redirections
- (list (list 'ignore
- (append (car tt) (list (cadr tt))))))
- (setcdr tl (cddr tt))
- (setq tt (cddr tt))))
+ (cond
+ ;; Strip `eshell-copy-output-handle'.
+ ((and (consp (car tt))
+ (eq (caar tt) 'eshell-copy-output-handle))
+ (nconc eshell-current-redirections
+ (list (car tt)))
+ (setcdr tl (cddr tt))
+ (setq tt (cdr tt)))
+ ;; Strip `eshell-set-output-handle' or
+ ;; `eshell-set-all-output-handles' and the term immediately
+ ;; after (the redirection target).
+ ((and (consp (car tt))
+ (memq (caar tt) '(eshell-set-output-handle
+ eshell-set-all-output-handles)))
+ (unless (cdr tt)
+ (error "Missing redirection target"))
+ (nconc eshell-current-redirections
+ (list (list 'ignore
+ (append (car tt) (list (cadr tt))))))
+ (setcdr tl (cddr tt))
+ (setq tt (cddr tt)))
+ (t
+ (setq tt (cdr tt)
+ tl (cdr tl)))))
(setq eshell-current-redirections
- (cdr eshell-current-redirections))))
+ (cdr eshell-current-redirections))))
(defun eshell--apply-redirections (cmd)
"Apply any redirection which were specified for COMMAND."
@@ -295,6 +354,22 @@ If HANDLES is nil, use `eshell-current-handles'."
(aset handles index (cons nil 1)))
(setcar (aref handles index) current))))))
+(defun eshell-copy-output-handle (index index-to-copy &optional handles)
+ "Copy the handle INDEX-TO-COPY to INDEX for the current HANDLES.
+If HANDLES is nil, use `eshell-current-handles'."
+ (let* ((handles (or handles eshell-current-handles))
+ (handle-to-copy (car (aref handles index-to-copy))))
+ (setcar (aref handles index)
+ (if (listp handle-to-copy)
+ (copy-sequence handle-to-copy)
+ handle-to-copy))))
+
+(defun eshell-set-all-output-handles (mode &optional target handles)
+ "Set output and error HANDLES to point to TARGET using MODE.
+If HANDLES is nil, use `eshell-current-handles'."
+ (eshell-set-output-handle eshell-output-handle mode target handles)
+ (eshell-copy-output-handle eshell-error-handle eshell-output-handle handles))
+
(defun eshell-close-target (target status)
"Close an output TARGET, passing STATUS as the result.
STATUS should be non-nil on successful termination of the output."