From: Jim Porter Date: Thu, 23 May 2024 21:52:07 +0000 (-0700) Subject: Add ability for Eshell virtual targets to handle closing the target X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=8dc5f8a6843b00ea7aba4b3d0c5d72322966e85e;p=emacs.git 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. (cherry picked from commit 9daf1085a9b11e056079edce8dccca92d69bf891) --- 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. @end table @vindex eshell-virtual-targets -You can, of course, define your own virtual targets. They are defined -by adding a list of the form @samp{("/dev/name" @var{function} -@var{mode})} to @code{eshell-virtual-targets}. The first element is -the device name; @var{function} may be either a lambda or a function -name. If @var{mode} is @code{nil}, then the function is the output -function; if it is non-@code{nil}, then the function is passed the -redirection mode as a symbol--@code{overwrite} for @code{>}, -@code{append} for @code{>>}, or @code{insert} for @code{>>>}--and the -function is expected to return the output function. - -The output function is called once on each line of output until -@code{nil} is passed, indicating end of output. +@vindex eshell-generic-target +@findex eshell-output-object-to-target +@findex eshell-close-target +You can, of course, define your own virtual targets. These are entries +in @code{eshell-virtual-targets} with the form @samp{(@var{filename} +@var{output-function} @var{pass-mode})}. The first element, +@var{filename}, is the device name, usually of the form +@samp{"/dev/@var{name}"}. The second, @var{output-function}, should be a +function: Eshell will repeatedly call it with the redirected output. +This argument can also be an @code{eshell-generic-target} instance. In +this case, Eshell will repeatedly call the generic function +@code{eshell-output-object-to-target} with the output; once the +redirection has completed, Eshell will then call the generic function +@code{eshell-close-target}, passing non-@code{nil} if the redirected +command succeeded. + +If @var{pass-mode} is non-@code{nil}, then Eshell will pass the +redirection mode as an argument to @code{output-function} as a +symbol: @code{overwrite} for @code{>}, @code{append} for @code{>>}, or +@code{insert} for @code{>>>}. In this case, @code{output-function} +should return the real output function (either an ordinary function or +an @code{eshell-generic-target} as described above). + +@defun eshell-function-target-create output-function &optional close-function +Create a new virtual target for Eshell that repeatedly calls +@var{output-function} with the redirected output, as described above. +If @var{close-function} is non-nil, Eshell will call it when closing the +target, passing non-@code{nil} if the redirected command succeeded. +@end defun @node Pipelines @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." #'eshell-clipboard-append) t)) "Map virtual devices name to Emacs Lisp functions. -If the user specifies any of the filenames above as a redirection -target, the function in the second element will be called. - -If the third element is non-nil, the redirection mode is passed as an -argument (which is the symbol `overwrite', `append' or `insert'), and -the function is expected to return another function -- which is the -output function. Otherwise, the second element itself is the output -function. - -The output function is then called repeatedly with single strings, -which represents successive pieces of the output of the command, until nil -is passed, meaning EOF." +Each member is of the following form: + + (FILENAME OUTPUT-FUNCTION [PASS-MODE]) + +When the user specifies FILENAME as a redirection target, Eshell will +repeatedly call the OUTPUT-FUNCTION with the redirected output as +strings. OUTPUT-FUNCTION can also be an `eshell-generic-target' +instance. In this case, Eshell will repeatedly call the function in the +`output-function' slot with the string output; once the redirection has +completed, Eshell will then call the function in the `close-function' +slot, passing the exit status of the redirected command. + +If PASS-MODE is non-nil, Eshell will pass the redirection mode as an +argument (which is the symbol `overwrite', `append' or `insert') to +OUTPUT-FUNCTION, which should return the real output function (either an +ordinary function or `eshell-generic-target' as desribed above)." :version "30.1" :type '(repeat (list (string :tag "Target") @@ -499,11 +503,18 @@ after all printing is over with no argument." (eshell-print object) (eshell-print "\n")) -(cl-defstruct (eshell-virtual-target +(cl-defstruct (eshell-generic-target (:constructor nil)) + "An Eshell target. +This is mainly useful for creating virtual targets (see +`eshell-virtual-targets').") + +(cl-defstruct (eshell-function-target + (:include eshell-generic-target) (:constructor nil) - (:constructor eshell-virtual-target-create (output-function))) - "A virtual target (see `eshell-virtual-targets')." - output-function) + (:constructor eshell-function-target-create + (output-function &optional close-function))) + "An Eshell target that calls an OUTPUT-FUNCTION." + output-function close-function) (cl-defgeneric eshell-get-target (raw-target &optional _mode) "Convert RAW-TARGET, which is a raw argument, into a valid output target. @@ -514,14 +525,16 @@ it defaults to `insert'." (cl-defmethod eshell-get-target ((raw-target string) &optional mode) "Convert a string RAW-TARGET into a valid output target using MODE. If TARGET is a virtual target (see `eshell-virtual-targets'), -return an `eshell-virtual-target' instance; otherwise, return a +return an `eshell-generic-target' instance; otherwise, return a marker for a file named TARGET." (setq mode (or mode 'insert)) (if-let ((redir (assoc raw-target eshell-virtual-targets))) - (eshell-virtual-target-create - (if (nth 2 redir) - (funcall (nth 1 redir) mode) - (nth 1 redir))) + (let ((target (if (nth 2 redir) + (funcall (nth 1 redir) mode) + (nth 1 redir)))) + (unless (eshell-generic-target-p target) + (setq target (eshell-function-target-create target))) + target) (let ((exists (get-file-buffer raw-target)) (buf (find-file-noselect raw-target t))) (with-current-buffer buf @@ -602,9 +615,10 @@ If status is nil, prompt before killing." (throw 'done nil)) (process-send-eof target)))) -(cl-defmethod eshell-close-target ((_target eshell-virtual-target) _status) - "Close a virtual TARGET." - nil) +(cl-defmethod eshell-close-target ((target eshell-function-target) status) + "Close an Eshell function TARGET." + (when-let ((close-function (eshell-function-target-close-function target))) + (funcall close-function status))) (cl-defgeneric eshell-output-object-to-target (object target) "Output OBJECT to TARGET. @@ -660,9 +674,9 @@ Returns what was actually sent, or nil if nothing was sent.") object) (cl-defmethod eshell-output-object-to-target (object - (target eshell-virtual-target)) - "Output OBJECT to the virtual TARGET." - (funcall (eshell-virtual-target-output-function target) object)) + (target eshell-function-target)) + "Output OBJECT to the Eshell function TARGET." + (funcall (eshell-function-target-output-function target) object)) (defun eshell-output-object (object &optional handle-index handles) "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)." (eshell-insert-command "echo three >> /dev/kill") (should (equal (car kill-ring) "twothree")))) +(ert-deftest esh-io-test/virtual/device-close () + "Check that the close function for `eshell-function-target' works." + (let* ((data nil) + (status nil) + (eshell-virtual-targets + `(("/dev/virtual" + ,(eshell-function-target-create + (lambda (d) (setq data d)) + (lambda (s) (setq status s))) + nil)))) + (with-temp-eshell + (eshell-insert-command "echo hello > /dev/virtual") + (should (equal data "hello")) + (should (equal status t))))) + ;;; esh-io-tests.el ends here