mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-23 07:19:15 +00:00
Fix calling Eshell scripts outside of Eshell
* lisp/eshell/em-script.el (eshell-source-file): Make obsolete. (eshell--source-file): Adapt from 'eshell-source-file'... (eshell-script-initialize, eshell/source, eshell/.): ... use it. (eshell-princ-target): New struct. (eshell-output-object-to-target, eshell-target-line-oriented-p): New implementations for 'eshell-princ-target'. (eshell-execute-file, eshell-batch-file): New functions. * lisp/eshell/esh-mode.el (eshell-mode): Just warn if we can't create the Eshell directory. * test/lisp/eshell/em-script-tests.el (em-script-test/execute-file): (em-script-test/execute-file/args), em-script-test/batch-file): New tests. * test/lisp/eshell/eshell-tests-helpers.el (with-temp-eshell-settings): New function... (with-temp-eshell): ... use it. * doc/misc/eshell.texi (Control Flow): Update documentation. * etc/NEWS: Announce this change (bug#70847).
This commit is contained in:
parent
eac608cb80
commit
9280a619ab
@ -1686,13 +1686,20 @@ treat it as a list of one element. If you specify multiple
|
||||
@node Scripts
|
||||
@section Scripts
|
||||
@cmindex source
|
||||
@fnindex eshell-source-file
|
||||
@fnindex eshell-execute-file
|
||||
@fnindex eshell-batch-file
|
||||
You can run Eshell scripts much like scripts for other shells; the main
|
||||
difference is that since Eshell is not a system command, you have to run
|
||||
it from within Emacs. An Eshell script is simply a file containing a
|
||||
sequence of commands, as with almost any other shell script. Scripts
|
||||
are invoked from Eshell with @command{source}, or from anywhere in Emacs
|
||||
with @code{eshell-source-file}.
|
||||
sequence of commands, as with almost any other shell script. You can
|
||||
invoke scripts from within Eshell with @command{source}, or from
|
||||
anywhere in Emacs with @code{eshell-execute-file}. Additionally, you
|
||||
can make an Eshell script file executable by calling
|
||||
@code{eshell-batch-file} in the interpreter directive:
|
||||
|
||||
@example
|
||||
#!/usr/bin/env -S emacs --batch -f eshell-batch-file
|
||||
@end example
|
||||
|
||||
Like with aliases (@pxref{Aliases}), Eshell scripts can accept any
|
||||
number of arguments. Within the script, you can refer to these with
|
||||
|
7
etc/NEWS
7
etc/NEWS
@ -870,6 +870,13 @@ using this new option. (Or set 'display-buffer-alist' directly.)
|
||||
|
||||
** Eshell
|
||||
|
||||
+++
|
||||
*** You can now run Eshell scripts in batch mode.
|
||||
By adding the following interpreter directive to an Eshell script, you
|
||||
can make it executable like other shell scripts:
|
||||
|
||||
#!/usr/bin/env -S emacs --batch -f eshell-batch-file
|
||||
|
||||
+++
|
||||
*** New builtin Eshell command 'compile'.
|
||||
This command runs another command, sending its output to a compilation
|
||||
|
@ -24,6 +24,7 @@
|
||||
;;; Code:
|
||||
|
||||
(require 'esh-mode)
|
||||
(require 'esh-io)
|
||||
|
||||
;;;###esh-module-autoload
|
||||
(progn
|
||||
@ -75,42 +76,106 @@ This includes when running `eshell-command'."
|
||||
eshell-login-script
|
||||
(file-readable-p eshell-login-script)
|
||||
(eshell-do-eval
|
||||
(list 'eshell-commands
|
||||
(catch 'eshell-replace-command
|
||||
(eshell-source-file eshell-login-script)))
|
||||
`(eshell-commands ,(eshell--source-file eshell-login-script))
|
||||
t))
|
||||
(and eshell-rc-script
|
||||
(file-readable-p eshell-rc-script)
|
||||
(eshell-do-eval
|
||||
(list 'eshell-commands
|
||||
(catch 'eshell-replace-command
|
||||
(eshell-source-file eshell-rc-script))) t))))
|
||||
`(eshell-commands ,(eshell--source-file eshell-rc-script))
|
||||
t))))
|
||||
|
||||
(defun eshell--source-file (file &optional args subcommand-p)
|
||||
"Return a Lisp form for executig the Eshell commands in FILE, passing ARGS.
|
||||
If SUBCOMMAND-P is non-nil, execute this as a subcommand."
|
||||
(let ((cmd (eshell-parse-command `(:file . ,file))))
|
||||
(when subcommand-p
|
||||
(setq cmd `(eshell-as-subcommand ,cmd)))
|
||||
`(let ((eshell-command-name ',file)
|
||||
(eshell-command-arguments ',args)
|
||||
;; Don't print subjob messages by default. Otherwise, if
|
||||
;; this function was called as a subjob, then *all* commands
|
||||
;; in the script would print start/stop messages.
|
||||
(eshell-subjob-messages nil))
|
||||
,cmd)))
|
||||
|
||||
(defun eshell-source-file (file &optional args subcommand-p)
|
||||
"Execute a series of Eshell commands in FILE, passing ARGS.
|
||||
Comments begin with `#'."
|
||||
(let ((cmd (eshell-parse-command `(:file . ,file))))
|
||||
(when subcommand-p
|
||||
(setq cmd `(eshell-as-subcommand ,cmd)))
|
||||
(throw 'eshell-replace-command
|
||||
`(let ((eshell-command-name ',file)
|
||||
(eshell-command-arguments ',args)
|
||||
;; Don't print subjob messages by default.
|
||||
;; Otherwise, if this function was called as a
|
||||
;; subjob, then *all* commands in the script would
|
||||
;; print start/stop messages.
|
||||
(eshell-subjob-messages nil))
|
||||
,cmd))))
|
||||
(declare (obsolete nil "30.1"))
|
||||
(throw 'eshell-replace-command
|
||||
(eshell--source-file file args subcommand-p)))
|
||||
|
||||
(defun eshell/source (&rest args)
|
||||
"Source a file in a subshell environment."
|
||||
(eshell-source-file (car args) (cdr args) t))
|
||||
;;;###autoload
|
||||
(defun eshell-execute-file (file &optional args destination)
|
||||
"Execute a series of Eshell commands in FILE, passing ARGS.
|
||||
Comments begin with `#'."
|
||||
(let ((eshell-non-interactive-p t)
|
||||
(stdout (if (eq destination t) (current-buffer) destination)))
|
||||
(with-temp-buffer
|
||||
(eshell-mode)
|
||||
(eshell-do-eval
|
||||
`(let ((eshell-current-handles
|
||||
(eshell-create-handles ,stdout 'insert))
|
||||
(eshell-current-subjob-p))
|
||||
,(eshell--source-file file args))
|
||||
t))))
|
||||
|
||||
(cl-defstruct (eshell-princ-target
|
||||
(:include eshell-generic-target)
|
||||
(:constructor nil)
|
||||
(:constructor eshell-princ-target-create
|
||||
(&optional printcharfun)))
|
||||
"A virtual target calling `princ' (see `eshell-virtual-targets')."
|
||||
printcharfun)
|
||||
|
||||
(cl-defmethod eshell-output-object-to-target (object
|
||||
(target eshell-princ-target))
|
||||
"Output OBJECT to the `princ' function TARGET."
|
||||
(princ object (eshell-princ-target-printcharfun target)))
|
||||
|
||||
(cl-defmethod eshell-target-line-oriented-p ((_target eshell-princ-target))
|
||||
"Return non-nil to indicate that the display is line-oriented."
|
||||
t)
|
||||
|
||||
;;;###autoload
|
||||
(defun eshell-batch-file ()
|
||||
"Execute an Eshell script as a batch script from the command line.
|
||||
Inside your Eshell script file, you can add the following at the
|
||||
top in order to make it into an executable script:
|
||||
|
||||
#!/usr/bin/env -S emacs --batch -f eshell-batch-file"
|
||||
(let ((file (pop command-line-args-left))
|
||||
(args command-line-args-left)
|
||||
(eshell-non-interactive-p t)
|
||||
(eshell-module-loading-messages nil)
|
||||
(eshell-virtual-targets
|
||||
(append `(("/dev/stdout" ,(eshell-princ-target-create) nil)
|
||||
("/dev/stderr" ,(eshell-princ-target-create
|
||||
#'external-debugging-output)
|
||||
nil))
|
||||
eshell-virtual-targets)))
|
||||
(setq command-line-args-left nil)
|
||||
(with-temp-buffer
|
||||
(eshell-mode)
|
||||
(eshell-do-eval
|
||||
`(let ((eshell-current-handles
|
||||
(eshell-create-handles "/dev/stdout" 'append
|
||||
"/dev/stderr" 'append))
|
||||
(eshell-current-subjob-p))
|
||||
,(eshell--source-file file args))
|
||||
t))))
|
||||
|
||||
(defun eshell/source (file &rest args)
|
||||
"Source a FILE in a subshell environment."
|
||||
(throw 'eshell-replace-command
|
||||
(eshell--source-file file args t)))
|
||||
|
||||
(put 'eshell/source 'eshell-no-numeric-conversions t)
|
||||
|
||||
(defun eshell/. (&rest args)
|
||||
"Source a file in the current environment."
|
||||
(eshell-source-file (car args) (cdr args)))
|
||||
(defun eshell/. (file &rest args)
|
||||
"Source a FILE in the current environment."
|
||||
(throw 'eshell-replace-command
|
||||
(eshell--source-file file args)))
|
||||
|
||||
(put 'eshell/. 'eshell-no-numeric-conversions t)
|
||||
|
||||
|
@ -376,7 +376,8 @@ and the hook `eshell-exit-hook'."
|
||||
(eshell-load-modules eshell-modules-list)
|
||||
|
||||
(unless (file-exists-p eshell-directory-name)
|
||||
(eshell-make-private-directory eshell-directory-name t))
|
||||
(with-demoted-errors "Error creating Eshell directory: %s"
|
||||
(eshell-make-private-directory eshell-directory-name t)))
|
||||
|
||||
;; Initialize core Eshell modules, then extension modules, for this session.
|
||||
(eshell-initialize-modules (eshell-subgroups 'eshell))
|
||||
|
@ -24,6 +24,7 @@
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'ert-x)
|
||||
(require 'esh-mode)
|
||||
(require 'eshell)
|
||||
(require 'em-script)
|
||||
@ -94,4 +95,34 @@
|
||||
(eshell-match-command-output (format "source %s a b c" temp-file)
|
||||
"a\nb\nc\n"))))
|
||||
|
||||
(ert-deftest em-script-test/execute-file ()
|
||||
"Test running an Eshell script file via `eshell-execute-file'."
|
||||
(ert-with-temp-file temp-file
|
||||
:text "echo hi\necho bye"
|
||||
(with-temp-buffer
|
||||
(with-temp-eshell-settings
|
||||
(eshell-execute-file temp-file nil t))
|
||||
(should (equal (buffer-string) "hibye")))))
|
||||
|
||||
(ert-deftest em-script-test/execute-file/args ()
|
||||
"Test running an Eshell script file with args via `eshell-execute-file'."
|
||||
(ert-with-temp-file temp-file
|
||||
:text "+ $@*"
|
||||
(with-temp-buffer
|
||||
(with-temp-eshell-settings
|
||||
(eshell-execute-file temp-file '(1 2 3) t))
|
||||
(should (equal (buffer-string) "6")))))
|
||||
|
||||
(ert-deftest em-script-test/batch-file ()
|
||||
"Test running an Eshell script file as a batch script."
|
||||
(ert-with-temp-file temp-file
|
||||
:text (format
|
||||
"#!/usr/bin/env -S %s --batch -f eshell-batch-file\necho hi"
|
||||
(expand-file-name invocation-name invocation-directory))
|
||||
(set-file-modes temp-file #o744)
|
||||
(with-temp-buffer
|
||||
(with-temp-eshell-settings
|
||||
(call-process temp-file nil '(t nil)))
|
||||
(should (equal (buffer-string) "hi\n")))))
|
||||
|
||||
;; em-script-tests.el ends here
|
||||
|
@ -47,24 +47,30 @@ beginning of the test file."
|
||||
(file-directory-p ert-remote-temporary-file-directory)
|
||||
(file-writable-p ert-remote-temporary-file-directory))))
|
||||
|
||||
(defmacro with-temp-eshell-settings (&rest body)
|
||||
"Configure Eshell to leave no trace behind, and then evaluate BODY."
|
||||
(declare (indent 0))
|
||||
`(ert-with-temp-directory eshell-directory-name
|
||||
(let (;; We want no history file, so prevent Eshell from falling
|
||||
;; back on $HISTFILE.
|
||||
(process-environment (cons "HISTFILE" process-environment))
|
||||
;; Enable process debug instrumentation. We may be able to
|
||||
;; remove this eventually once we're confident that all the
|
||||
;; process bugs have been worked out. (At that point, we can
|
||||
;; just enable this selectively when needed.) See also
|
||||
;; `eshell-test-command-result' below.
|
||||
(eshell-debug-command (cons 'process eshell-debug-command))
|
||||
(eshell-history-file-name nil)
|
||||
(eshell-last-dir-ring-file-name nil)
|
||||
(eshell-module-loading-messages nil))
|
||||
,@body)))
|
||||
|
||||
(defmacro with-temp-eshell (&rest body)
|
||||
"Evaluate BODY in a temporary Eshell buffer."
|
||||
(declare (indent 0))
|
||||
`(save-current-buffer
|
||||
(ert-with-temp-directory eshell-directory-name
|
||||
(let* (;; We want no history file, so prevent Eshell from falling
|
||||
;; back on $HISTFILE.
|
||||
(process-environment (cons "HISTFILE" process-environment))
|
||||
;; Enable process debug instrumentation. We may be able
|
||||
;; to remove this eventually once we're confident that
|
||||
;; all the process bugs have been worked out. (At that
|
||||
;; point, we can just enable this selectively when
|
||||
;; needed.) See also `eshell-test-command-result'
|
||||
;; below.
|
||||
(eshell-debug-command (cons 'process eshell-debug-command))
|
||||
(eshell-history-file-name nil)
|
||||
(eshell-last-dir-ring-file-name nil)
|
||||
(eshell-module-loading-messages nil)
|
||||
(eshell-buffer (eshell t)))
|
||||
(with-temp-eshell-settings
|
||||
(let ((eshell-buffer (eshell t)))
|
||||
(unwind-protect
|
||||
(with-current-buffer eshell-buffer
|
||||
,@body)
|
||||
|
Loading…
Reference in New Issue
Block a user