]> git.eshelyaron.com Git - emacs.git/commitdiff
Add ability for Eshell virtual targets to handle closing the target
authorJim Porter <jporterbugs@gmail.com>
Thu, 23 May 2024 21:52:07 +0000 (14:52 -0700)
committerEshel Yaron <me@eshelyaron.com>
Thu, 30 May 2024 14:27:07 +0000 (16:27 +0200)
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)

doc/misc/eshell.texi
lisp/eshell/esh-io.el
test/lisp/eshell/esh-io-tests.el

index 57ee3bf3e9f6a2e2a3c439f4ea18f5221ebdd937..2da132e01eb74ae7ad223e39a574f748fc941ef0 100644 (file)
@@ -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
index 4487389bf26d5092177fe57b3de7ee7217973b06..9b35125cd28e6e99f139d0a86a401e2af5c5b06b 100644 (file)
@@ -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.
index 188570161c7f14e34258e1bd3fed99475f464b39..b4e8c0b4a9aac97de4ebba84cf9615ec7bd43d92 100644 (file)
@@ -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