aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Porter2024-05-23 14:52:07 -0700
committerJim Porter2024-05-29 12:09:05 -0700
commit9daf1085a9b11e056079edce8dccca92d69bf891 (patch)
tree955af8fdd760efca90732a9742e04d4d2f5a6712
parent4c924a53334035dc4089b24174012b54c020631b (diff)
downloademacs-9daf1085a9b11e056079edce8dccca92d69bf891.tar.gz
emacs-9daf1085a9b11e056079edce8dccca92d69bf891.zip
Add ability for Eshell virtual targets to handle closing the target
This was documented to work by calling the output function with 'nil', but that was never actually implemented. Instead, for compatibility, we now support a new (optional) close function. * lisp/eshell/esh-io.el (eshell-virtual-targets): Update docstring. (eshell-generic-target): New struct... (eshell-function-target): ... inherit from it, and rename from 'eshell-virtual-target'. (eshell-get-target): Handle already-created 'eshell-generic-target'. (eshell-close-target): Call the target's close function if present. * test/lisp/eshell/esh-io-tests.el (esh-io-test/virtual/device-close): New test. * doc/misc/eshell.texi (Redirection): Document the new behavior.
-rw-r--r--doc/misc/eshell.texi41
-rw-r--r--lisp/eshell/esh-io.el68
-rw-r--r--test/lisp/eshell/esh-io-tests.el15
3 files changed, 85 insertions, 39 deletions
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 57ee3bf3e9f..2da132e01eb 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -2392,18 +2392,35 @@ Adds the text passed to it to the clipboard.
2392@end table 2392@end table
2393 2393
2394@vindex eshell-virtual-targets 2394@vindex eshell-virtual-targets
2395You can, of course, define your own virtual targets. They are defined 2395@vindex eshell-generic-target
2396by adding a list of the form @samp{("/dev/name" @var{function} 2396@findex eshell-output-object-to-target
2397@var{mode})} to @code{eshell-virtual-targets}. The first element is 2397@findex eshell-close-target
2398the device name; @var{function} may be either a lambda or a function 2398You can, of course, define your own virtual targets. These are entries
2399name. If @var{mode} is @code{nil}, then the function is the output 2399in @code{eshell-virtual-targets} with the form @samp{(@var{filename}
2400function; if it is non-@code{nil}, then the function is passed the 2400@var{output-function} @var{pass-mode})}. The first element,
2401redirection mode as a symbol--@code{overwrite} for @code{>}, 2401@var{filename}, is the device name, usually of the form
2402@code{append} for @code{>>}, or @code{insert} for @code{>>>}--and the 2402@samp{"/dev/@var{name}"}. The second, @var{output-function}, should be a
2403function is expected to return the output function. 2403function: Eshell will repeatedly call it with the redirected output.
2404 2404This argument can also be an @code{eshell-generic-target} instance. In
2405The output function is called once on each line of output until 2405this case, Eshell will repeatedly call the generic function
2406@code{nil} is passed, indicating end of output. 2406@code{eshell-output-object-to-target} with the output; once the
2407redirection has completed, Eshell will then call the generic function
2408@code{eshell-close-target}, passing non-@code{nil} if the redirected
2409command succeeded.
2410
2411If @var{pass-mode} is non-@code{nil}, then Eshell will pass the
2412redirection mode as an argument to @code{output-function} as a
2413symbol: @code{overwrite} for @code{>}, @code{append} for @code{>>}, or
2414@code{insert} for @code{>>>}. In this case, @code{output-function}
2415should return the real output function (either an ordinary function or
2416an @code{eshell-generic-target} as described above).
2417
2418@defun eshell-function-target-create output-function &optional close-function
2419Create a new virtual target for Eshell that repeatedly calls
2420@var{output-function} with the redirected output, as described above.
2421If @var{close-function} is non-nil, Eshell will call it when closing the
2422target, passing non-@code{nil} if the redirected command succeeded.
2423@end defun
2407 2424
2408@node Pipelines 2425@node Pipelines
2409@section Pipelines 2426@section Pipelines
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 4487389bf26..9b35125cd28 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -135,18 +135,22 @@ from executing while Emacs is redisplaying."
135 #'eshell-clipboard-append) 135 #'eshell-clipboard-append)
136 t)) 136 t))
137 "Map virtual devices name to Emacs Lisp functions. 137 "Map virtual devices name to Emacs Lisp functions.
138If the user specifies any of the filenames above as a redirection 138Each member is of the following form:
139target, the function in the second element will be called. 139
140 140 (FILENAME OUTPUT-FUNCTION [PASS-MODE])
141If the third element is non-nil, the redirection mode is passed as an 141
142argument (which is the symbol `overwrite', `append' or `insert'), and 142When the user specifies FILENAME as a redirection target, Eshell will
143the function is expected to return another function -- which is the 143repeatedly call the OUTPUT-FUNCTION with the redirected output as
144output function. Otherwise, the second element itself is the output 144strings. OUTPUT-FUNCTION can also be an `eshell-generic-target'
145function. 145instance. In this case, Eshell will repeatedly call the function in the
146 146`output-function' slot with the string output; once the redirection has
147The output function is then called repeatedly with single strings, 147completed, Eshell will then call the function in the `close-function'
148which represents successive pieces of the output of the command, until nil 148slot, passing the exit status of the redirected command.
149is passed, meaning EOF." 149
150If PASS-MODE is non-nil, Eshell will pass the redirection mode as an
151argument (which is the symbol `overwrite', `append' or `insert') to
152OUTPUT-FUNCTION, which should return the real output function (either an
153ordinary function or `eshell-generic-target' as desribed above)."
150 :version "30.1" 154 :version "30.1"
151 :type '(repeat 155 :type '(repeat
152 (list (string :tag "Target") 156 (list (string :tag "Target")
@@ -499,11 +503,18 @@ after all printing is over with no argument."
499 (eshell-print object) 503 (eshell-print object)
500 (eshell-print "\n")) 504 (eshell-print "\n"))
501 505
502(cl-defstruct (eshell-virtual-target 506(cl-defstruct (eshell-generic-target (:constructor nil))
507 "An Eshell target.
508This is mainly useful for creating virtual targets (see
509`eshell-virtual-targets').")
510
511(cl-defstruct (eshell-function-target
512 (:include eshell-generic-target)
503 (:constructor nil) 513 (:constructor nil)
504 (:constructor eshell-virtual-target-create (output-function))) 514 (:constructor eshell-function-target-create
505 "A virtual target (see `eshell-virtual-targets')." 515 (output-function &optional close-function)))
506 output-function) 516 "An Eshell target that calls an OUTPUT-FUNCTION."
517 output-function close-function)
507 518
508(cl-defgeneric eshell-get-target (raw-target &optional _mode) 519(cl-defgeneric eshell-get-target (raw-target &optional _mode)
509 "Convert RAW-TARGET, which is a raw argument, into a valid output target. 520 "Convert RAW-TARGET, which is a raw argument, into a valid output target.
@@ -514,14 +525,16 @@ it defaults to `insert'."
514(cl-defmethod eshell-get-target ((raw-target string) &optional mode) 525(cl-defmethod eshell-get-target ((raw-target string) &optional mode)
515 "Convert a string RAW-TARGET into a valid output target using MODE. 526 "Convert a string RAW-TARGET into a valid output target using MODE.
516If TARGET is a virtual target (see `eshell-virtual-targets'), 527If TARGET is a virtual target (see `eshell-virtual-targets'),
517return an `eshell-virtual-target' instance; otherwise, return a 528return an `eshell-generic-target' instance; otherwise, return a
518marker for a file named TARGET." 529marker for a file named TARGET."
519 (setq mode (or mode 'insert)) 530 (setq mode (or mode 'insert))
520 (if-let ((redir (assoc raw-target eshell-virtual-targets))) 531 (if-let ((redir (assoc raw-target eshell-virtual-targets)))
521 (eshell-virtual-target-create 532 (let ((target (if (nth 2 redir)
522 (if (nth 2 redir) 533 (funcall (nth 1 redir) mode)
523 (funcall (nth 1 redir) mode) 534 (nth 1 redir))))
524 (nth 1 redir))) 535 (unless (eshell-generic-target-p target)
536 (setq target (eshell-function-target-create target)))
537 target)
525 (let ((exists (get-file-buffer raw-target)) 538 (let ((exists (get-file-buffer raw-target))
526 (buf (find-file-noselect raw-target t))) 539 (buf (find-file-noselect raw-target t)))
527 (with-current-buffer buf 540 (with-current-buffer buf
@@ -602,9 +615,10 @@ If status is nil, prompt before killing."
602 (throw 'done nil)) 615 (throw 'done nil))
603 (process-send-eof target)))) 616 (process-send-eof target))))
604 617
605(cl-defmethod eshell-close-target ((_target eshell-virtual-target) _status) 618(cl-defmethod eshell-close-target ((target eshell-function-target) status)
606 "Close a virtual TARGET." 619 "Close an Eshell function TARGET."
607 nil) 620 (when-let ((close-function (eshell-function-target-close-function target)))
621 (funcall close-function status)))
608 622
609(cl-defgeneric eshell-output-object-to-target (object target) 623(cl-defgeneric eshell-output-object-to-target (object target)
610 "Output OBJECT to TARGET. 624 "Output OBJECT to TARGET.
@@ -660,9 +674,9 @@ Returns what was actually sent, or nil if nothing was sent.")
660 object) 674 object)
661 675
662(cl-defmethod eshell-output-object-to-target (object 676(cl-defmethod eshell-output-object-to-target (object
663 (target eshell-virtual-target)) 677 (target eshell-function-target))
664 "Output OBJECT to the virtual TARGET." 678 "Output OBJECT to the Eshell function TARGET."
665 (funcall (eshell-virtual-target-output-function target) object)) 679 (funcall (eshell-function-target-output-function target) object))
666 680
667(defun eshell-output-object (object &optional handle-index handles) 681(defun eshell-output-object (object &optional handle-index handles)
668 "Insert OBJECT, using HANDLE-INDEX specifically. 682 "Insert OBJECT, using HANDLE-INDEX specifically.
diff --git a/test/lisp/eshell/esh-io-tests.el b/test/lisp/eshell/esh-io-tests.el
index 188570161c7..b4e8c0b4a9a 100644
--- a/test/lisp/eshell/esh-io-tests.el
+++ b/test/lisp/eshell/esh-io-tests.el
@@ -381,4 +381,19 @@ stdout originally pointed (the terminal)."
381 (eshell-insert-command "echo three >> /dev/kill") 381 (eshell-insert-command "echo three >> /dev/kill")
382 (should (equal (car kill-ring) "twothree")))) 382 (should (equal (car kill-ring) "twothree"))))
383 383
384(ert-deftest esh-io-test/virtual/device-close ()
385 "Check that the close function for `eshell-function-target' works."
386 (let* ((data nil)
387 (status nil)
388 (eshell-virtual-targets
389 `(("/dev/virtual"
390 ,(eshell-function-target-create
391 (lambda (d) (setq data d))
392 (lambda (s) (setq status s)))
393 nil))))
394 (with-temp-eshell
395 (eshell-insert-command "echo hello > /dev/virtual")
396 (should (equal data "hello"))
397 (should (equal status t)))))
398
384;;; esh-io-tests.el ends here 399;;; esh-io-tests.el ends here