deletions) are always permanent. This approach was chosen for the
sake of simplicity, since that's pretty much the only benefit to be
gained by using this module."
- :type 'file
+ :version "30.1"
+ :type '(choice (const :tag "Don't save aliases" nil)
+ file)
:group 'eshell-alias)
(defcustom eshell-bad-command-tolerance 3
"Read in an aliases list from `eshell-aliases-file'.
This is useful after manually editing the contents of the file."
(interactive)
- (let ((file eshell-aliases-file))
- (when (file-readable-p file)
- (setq eshell-command-aliases-list
- (with-temp-buffer
- (let (eshell-command-aliases-list)
- (insert-file-contents file)
- (while (not (eobp))
- (if (re-search-forward
- "^alias\\s-+\\(\\S-+\\)\\s-+\\(.+\\)")
- (setq eshell-command-aliases-list
- (cons (list (match-string 1)
- (match-string 2))
- eshell-command-aliases-list)))
- (forward-line 1))
- eshell-command-aliases-list))))))
+ (when (and eshell-aliases-file
+ (file-readable-p eshell-aliases-file))
+ (setq eshell-command-aliases-list
+ (with-temp-buffer
+ (let (eshell-command-aliases-list)
+ (insert-file-contents eshell-aliases-file)
+ (while (not (eobp))
+ (if (re-search-forward
+ "^alias\\s-+\\(\\S-+\\)\\s-+\\(.+\\)")
+ (setq eshell-command-aliases-list
+ (cons (list (match-string 1)
+ (match-string 2))
+ eshell-command-aliases-list)))
+ (forward-line 1))
+ eshell-command-aliases-list)))))
(defun eshell-write-aliases-list ()
"Write out the current aliases into `eshell-aliases-file'."
- (if (file-writable-p (file-name-directory eshell-aliases-file))
- (let ((eshell-current-handles
- (eshell-create-handles eshell-aliases-file 'overwrite)))
- (eshell/alias)
- (eshell-close-handles 0 'nil))))
+ (when (and eshell-aliases-file
+ (file-writable-p (file-name-directory eshell-aliases-file)))
+ (let ((eshell-current-handles
+ (eshell-create-handles eshell-aliases-file 'overwrite)))
+ (eshell/alias)
+ (eshell-close-handles 0 'nil))))
(defsubst eshell-lookup-alias (name)
"Check whether NAME is aliased. Return the alias if there is one."
(defvar eshell-prevent-alias-expansion nil)
+(defun eshell-maybe-replace-by-alias--which (command)
+ (unless (and eshell-prevent-alias-expansion
+ (member command eshell-prevent-alias-expansion))
+ (when-let ((alias (eshell-lookup-alias command)))
+ (concat command " is an alias, defined as \"" (cadr alias) "\""))))
+
(defun eshell-maybe-replace-by-alias (command _args)
"Call COMMAND's alias definition, if it exists."
(unless (and eshell-prevent-alias-expansion
(member command eshell-prevent-alias-expansion))
- (let ((alias (eshell-lookup-alias command)))
- (if alias
- (throw 'eshell-replace-command
- `(let ((eshell-command-name ',eshell-last-command-name)
- (eshell-command-arguments ',eshell-last-arguments)
- (eshell-prevent-alias-expansion
- ',(cons command eshell-prevent-alias-expansion)))
- ,(eshell-parse-command (nth 1 alias))))))))
+ (when-let ((alias (eshell-lookup-alias command)))
+ (throw 'eshell-replace-command
+ `(let ((eshell-command-name ',eshell-last-command-name)
+ (eshell-command-arguments ',eshell-last-arguments)
+ (eshell-prevent-alias-expansion
+ ',(cons command eshell-prevent-alias-expansion)))
+ ,(eshell-parse-command (nth 1 alias)))))))
+
+(put 'eshell-maybe-replace-by-alias 'eshell-which-function
+ #'eshell-maybe-replace-by-alias--which)
(defun eshell-alias-completions (name)
"Find all possible completions for NAME.
:type 'hook)
(defcustom eshell-named-command-hook nil
- "A set of functions called before a named command is invoked.
+ "A set of functions called before
+a named command is invoked.
Each function will be passed the command name and arguments that were
passed to `eshell-named-command'.
Although useless, the above code will cause any non-glob, non-Lisp
command (i.e., `ls' as opposed to `*ls' or `(ls)') to be replaced by a
-call to `cd' using the arguments that were passed to the function."
+call to `cd' using the arguments that were passed to the function.
+
+When adding a function to this hook, you should also set the property
+`eshell-which-function' for the function. This property should hold a
+function that takes a single COMMAND argument and returns a string
+describing where Eshell will find the function."
:type 'hook)
(defcustom eshell-pre-rewrite-command-hook
(defun eshell/which (command &rest names)
"Identify the COMMAND, and where it is located."
(dolist (name (cons command names))
- (let (program alias direct)
- (if (eq (aref name 0) eshell-explicit-command-char)
- (setq name (substring name 1)
- direct t))
- (if (and (not direct)
- (fboundp 'eshell-lookup-alias)
- (setq alias
- (eshell-lookup-alias name)))
- (setq program
- (concat name " is an alias, defined as \""
- (cadr alias) "\"")))
- (unless program
- (setq program
- (let* ((esym (eshell-find-alias-function name))
- (sym (or esym (intern-soft name))))
- (if (and (or esym (and sym (fboundp sym)))
- (or eshell-prefer-lisp-functions (not direct)))
- (or (with-output-to-string
- (require 'help-fns)
- (princ (format "%s is " sym))
- (help-fns-function-description-header sym))
- name)
- (eshell-search-path name)))))
- (if (not program)
- (eshell-error (format "which: no %s in (%s)\n"
- name (string-join (eshell-get-path t)
- (path-separator))))
- (eshell-printn program)))))
+ (condition-case error
+ (eshell-printn
+ (catch 'found
+ (run-hook-wrapped
+ 'eshell-named-command-hook
+ (lambda (hook)
+ (when-let (((symbolp hook))
+ (which-func (get hook 'eshell-which-function))
+ (result (funcall which-func command)))
+ (throw 'found result))))
+ (eshell-plain-command--which name)))
+ (error (eshell-error (format "which: %s\n" (cadr error)))))))
(put 'eshell/which 'eshell-no-numeric-conversions t)
(if (functionp sym)
sym))))
+(defun eshell--find-plain-lisp-command (command)
+ "Look for `eshell/COMMAND' and return it when COMMAND should use it."
+ (let* ((esym (eshell-find-alias-function command))
+ (sym (or esym (intern-soft command))))
+ (when (and sym (fboundp sym)
+ (or esym eshell-prefer-lisp-functions
+ (not (eshell-search-path command))))
+ sym)))
+
+(defun eshell-plain-command--which (command)
+ (if-let ((sym (eshell--find-plain-lisp-command command)))
+ (or (with-output-to-string
+ (require 'help-fns)
+ (princ (format "%s is " sym))
+ (help-fns-function-description-header sym))
+ command)
+ (eshell-external-command--which command)))
+
(defun eshell-plain-command (command args)
"Insert output from a plain COMMAND, using ARGS.
COMMAND may result in either a Lisp function being executed by name,
or an external command."
- (let* ((esym (eshell-find-alias-function command))
- (sym (or esym (intern-soft command))))
- (if (and sym (fboundp sym)
- (or esym eshell-prefer-lisp-functions
- (not (eshell-search-path command))))
- (eshell-lisp-command sym args)
- (eshell-external-command command args))))
+ (if-let ((sym (eshell--find-plain-lisp-command command)))
+ (eshell-lisp-command sym args)
+ (eshell-external-command command args)))
(defun eshell-exec-lisp (printer errprint func-or-form args form-p)
"Execute a Lisp FUNC-OR-FORM, maybe passing ARGS.
(add-hook 'eshell-named-command-hook #'eshell-quoted-file-command nil t)
(add-hook 'eshell-named-command-hook #'eshell-explicit-command nil t))
+(defun eshell-explicit-command--which (command)
+ (when (and (> (length command) 1)
+ (eq (aref command 0) eshell-explicit-command-char))
+ (eshell-external-command--which (substring command 1))))
+
(defun eshell-explicit-command (command args)
"If a command name begins with \"*\", always call it externally.
This bypasses all Lisp functions and aliases."
(error "%s: external command not found"
(substring command 1))))))
+(put 'eshell-explicit-command 'eshell-which-function
+ #'eshell-explicit-command--which)
+
+(defun eshell-quoted-file-command--which (command)
+ (when (file-name-quoted-p command)
+ (eshell-external-command--which (file-name-unquote command))))
+
(defun eshell-quoted-file-command (command args)
"If a command name begins with \"/:\", always call it externally.
Similar to `eshell-explicit-command', this bypasses all Lisp functions
(when (file-name-quoted-p command)
(eshell-external-command (file-name-unquote command) args)))
+(put 'eshell-quoted-file-command 'eshell-which-function
+ #'eshell-quoted-file-command--which)
+
(defun eshell-remote-command (command args)
"Insert output from a remote COMMAND, using ARGS.
A \"remote\" command in Eshell is something that executes on a different
(eshell-gather-process-output
(car interp) (append (cdr interp) args)))))
+(defun eshell-external-command--which (command)
+ (or (eshell-search-path command)
+ (error "no %s in (%s)" command
+ (string-join (eshell-get-path t) (path-separator)))))
+
(defun eshell-external-command (command args)
"Insert output from an external COMMAND, using ARGS."
(cond
;; Make sure we can call another command after throwing.
(eshell-match-command-output "echo again" "\\`again\n")))
+\f
+;; `which' command
+
+(ert-deftest esh-cmd-test/which/plain/eshell-builtin ()
+ "Check that `which' finds Eshell built-in functions."
+ (eshell-command-result-match "which cat" "\\`eshell/cat"))
+
+(ert-deftest esh-cmd-test/which/plain/external-program ()
+ "Check that `which' finds external programs."
+ (skip-unless (executable-find "sh"))
+ (eshell-command-result-equal "which sh"
+ (concat (executable-find "sh") "\n")))
+
+(ert-deftest esh-cmd-test/which/plain/not-found ()
+ "Check that `which' reports an error for not-found commands."
+ (skip-when (executable-find "nonexist"))
+ (eshell-command-result-match "which nonexist" "\\`which: no nonexist in"))
+
+(ert-deftest esh-cmd-test/which/alias ()
+ "Check that `which' finds aliases."
+ (with-temp-eshell
+ (eshell-insert-command "alias cat '*cat $@*'")
+ (eshell-match-command-output "which cat" "\\`cat is an alias")))
+
+(ert-deftest esh-cmd-test/which/explicit ()
+ "Check that `which' finds explicitly-external programs."
+ (skip-unless (executable-find "cat"))
+ (eshell-command-result-match "which *cat"
+ (concat (executable-find "cat") "\n")))
+
+(ert-deftest esh-cmd-test/which/explicit/not-found ()
+ "Check that `which' reports an error for not-found explicit commands."
+ (skip-when (executable-find "nonexist"))
+ (eshell-command-result-match "which *nonexist" "\\`which: no nonexist in"))
+
+(ert-deftest esh-cmd-test/which/quoted-file ()
+ "Check that `which' finds programs with quoted file names."
+ (skip-unless (executable-find "cat"))
+ (eshell-command-result-match "which /:cat"
+ (concat (executable-find "cat") "\n")))
+
+(ert-deftest esh-cmd-test/which/quoted-file/not-found ()
+ "Check that `which' reports an error for not-found quoted commands."
+ (skip-when (executable-find "nonexist"))
+ (eshell-command-result-match "which /:nonexist" "\\`which: no nonexist in"))
+
;; esh-cmd-tests.el ends here
(require 'esh-mode)
(require 'eshell)
+(defvar eshell-aliases-file nil)
+(defvar eshell-command-aliases-list nil)
(defvar eshell-history-file-name nil)
(defvar eshell-last-dir-ring-file-name nil)
;; just enable this selectively when needed.) See also
;; `eshell-test-command-result' below.
(eshell-debug-command (cons 'process eshell-debug-command))
+ (eshell-aliases-file nil)
+ (eshell-command-aliases-list nil)
(eshell-history-file-name nil)
(eshell-last-dir-ring-file-name nil)
(eshell-module-loading-messages nil))
(eshell-test-command-result command)
result)))))
+(defun eshell-command-result--match (_command regexp actual)
+ "Compare the ACTUAL result of a COMMAND with REGEXP."
+ (string-match regexp actual))
+
+(defun eshell-command-result--match-explainer (command regexp actual)
+ "Explain the result of `eshell-command-result--match'."
+ `(mismatched-result
+ (command ,command)
+ (result ,actual)
+ (regexp ,regexp)))
+
+(put 'eshell-command-result--match 'ert-explainer
+ #'eshell-command-result--match-explainer)
+
+(defun eshell-command-result-match (command regexp)
+ "Execute COMMAND non-interactively and compare it to REGEXP."
+ (ert-info (#'eshell-get-debug-logs :prefix "Command logs: ")
+ (let ((eshell-module-loading-messages nil))
+ (should (eshell-command-result--match
+ command regexp
+ (eshell-test-command-result command))))))
+
(provide 'eshell-tests-helpers)
;;; eshell-tests-helpers.el ends here