esh-ext.el 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. ;;; esh-ext.el --- commands external to Eshell -*- lexical-binding:t -*-
  2. ;; Copyright (C) 1999-2015 Free Software Foundation, Inc.
  3. ;; Author: John Wiegley <johnw@gnu.org>
  4. ;; This file is part of GNU Emacs.
  5. ;; GNU Emacs is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; GNU Emacs is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; To force a command to invoked external, either provide an explicit
  17. ;; pathname for the command argument, or prefix the command name with
  18. ;; an asterix character. Example:
  19. ;;
  20. ;; grep ; make invoke `grep' Lisp function, or `eshell/grep'
  21. ;; /bin/grep ; will definitely invoke /bin/grep
  22. ;; *grep ; will also invoke /bin/grep
  23. ;;; Code:
  24. (provide 'esh-ext)
  25. (require 'esh-util)
  26. (eval-when-compile
  27. (require 'cl-lib)
  28. (require 'esh-io)
  29. (require 'esh-cmd))
  30. (require 'esh-opt)
  31. (defgroup eshell-ext nil
  32. "External commands are invoked when operating system executables are
  33. loaded into memory, thus beginning a new process."
  34. :tag "External commands"
  35. :group 'eshell)
  36. ;;; User Variables:
  37. (defcustom eshell-ext-load-hook nil
  38. "A hook that gets run when `eshell-ext' is loaded."
  39. :version "24.1" ; removed eshell-ext-initialize
  40. :type 'hook
  41. :group 'eshell-ext)
  42. (defcustom eshell-binary-suffixes exec-suffixes
  43. "A list of suffixes used when searching for executable files."
  44. :type '(repeat string)
  45. :group 'eshell-ext)
  46. (defcustom eshell-force-execution
  47. (not (null (memq system-type '(windows-nt ms-dos))))
  48. "If non-nil, try to execute files regardless of execute permissions.
  49. This can be useful on systems like Windows, where the operating system
  50. doesn't support the execution bit for shell scripts; or in cases where
  51. you want to associate an interpreter with a particular kind of script
  52. file, but the language won't let you but a `#!' interpreter line in
  53. the file, and you don't want to make it executable since nothing else
  54. but Eshell will be able to understand
  55. `eshell-interpreter-alist'."
  56. :type 'boolean
  57. :group 'eshell-ext)
  58. (defun eshell-search-path (name)
  59. "Search the environment path for NAME."
  60. (if (file-name-absolute-p name)
  61. name
  62. (let ((list (eshell-parse-colon-path eshell-path-env))
  63. suffixes n1 n2 file)
  64. (if (eshell-under-windows-p)
  65. (push "." list))
  66. (while list
  67. (setq n1 (concat (car list) name))
  68. (setq suffixes eshell-binary-suffixes)
  69. (while suffixes
  70. (setq n2 (concat n1 (car suffixes)))
  71. (if (and (or (file-executable-p n2)
  72. (and eshell-force-execution
  73. (file-readable-p n2)))
  74. (not (file-directory-p n2)))
  75. (setq file n2 suffixes nil list nil))
  76. (setq suffixes (cdr suffixes)))
  77. (setq list (cdr list)))
  78. file)))
  79. ;; This file provides itself then eval-when-compile loads files that require it.
  80. ;; This causes spurious "might not be defined at runtime" warnings.
  81. (declare-function eshell-search-path "esh-ext" (name))
  82. (defcustom eshell-windows-shell-file
  83. (if (eshell-under-windows-p)
  84. (if (string-match "\\(cmdproxy\\|sh\\)\\.\\(com\\|exe\\)"
  85. shell-file-name)
  86. (or (eshell-search-path "cmd.exe")
  87. (eshell-search-path "command.com"))
  88. shell-file-name))
  89. "The name of the shell command to use for DOS/Windows batch files.
  90. This defaults to nil on non-Windows systems, where this variable is
  91. wholly ignored."
  92. :type '(choice file (const nil))
  93. :group 'eshell-ext)
  94. (autoload 'eshell-parse-command "esh-cmd")
  95. (defsubst eshell-invoke-batch-file (&rest args)
  96. "Invoke a .BAT or .CMD file on DOS/Windows systems."
  97. ;; since CMD.EXE can't handle forward slashes in the initial
  98. ;; argument...
  99. (setcar args (subst-char-in-string ?/ ?\\ (car args)))
  100. (throw 'eshell-replace-command
  101. (eshell-parse-command
  102. (eshell-quote-argument eshell-windows-shell-file)
  103. (cons "/c" args))))
  104. (defcustom eshell-interpreter-alist
  105. (if (eshell-under-windows-p)
  106. '(("\\.\\(bat\\|cmd\\)\\'" . eshell-invoke-batch-file)))
  107. "An alist defining interpreter substitutions.
  108. Each member is a cons cell of the form:
  109. (MATCH . INTERPRETER)
  110. MATCH should be a regexp, which is matched against the command
  111. name, or a function of arity 2 receiving the COMMAND and its
  112. ARGS (a list). If either returns a non-nil value, then
  113. INTERPRETER will be used for that command.
  114. If INTERPRETER is a string, it will be called as the command name,
  115. with the original command name passed as the first argument, with all
  116. subsequent arguments following. If INTERPRETER is a function, it will
  117. be called with all of those arguments. Note that interpreter
  118. functions should throw `eshell-replace-command' with the alternate
  119. command form, or they should return a value compatible with the
  120. possible return values of `eshell-external-command', which see."
  121. :type '(repeat (cons (choice regexp (function :tag "Predicate"))
  122. (choice string (function :tag "Interpreter"))))
  123. :group 'eshell-ext)
  124. (defcustom eshell-alternate-command-hook nil
  125. "A hook run whenever external command lookup fails.
  126. If a functions wishes to provide an alternate command, they must throw
  127. it using the tag `eshell-replace-command'. This is done because the
  128. substituted command need not be external at all, and therefore must be
  129. passed up to a higher level for re-evaluation.
  130. Or, if the function returns a filename, that filename will be invoked
  131. with the current command arguments rather than the command specified
  132. by the user on the command line."
  133. :type 'hook
  134. :group 'eshell-ext)
  135. (defcustom eshell-command-interpreter-max-length 256
  136. "The maximum length of any command interpreter string, plus args."
  137. :type 'integer
  138. :group 'eshell-ext)
  139. (defcustom eshell-explicit-command-char ?*
  140. "If this char occurs before a command name, call it externally.
  141. That is, although `vi' may be an alias, `\vi' will always call the
  142. external version."
  143. :type 'character
  144. :group 'eshell-ext)
  145. ;;; Functions:
  146. (defun eshell-ext-initialize ()
  147. "Initialize the external command handling code."
  148. (add-hook 'eshell-named-command-hook 'eshell-explicit-command nil t))
  149. (defun eshell-explicit-command (command args)
  150. "If a command name begins with `*', call it externally always.
  151. This bypasses all Lisp functions and aliases."
  152. (when (and (> (length command) 1)
  153. (eq (aref command 0) eshell-explicit-command-char))
  154. (let ((cmd (eshell-search-path (substring command 1))))
  155. (if cmd
  156. (or (eshell-external-command cmd args)
  157. (error "%s: external command failed" cmd))
  158. (error "%s: external command not found"
  159. (substring command 1))))))
  160. (autoload 'eshell-close-handles "esh-io")
  161. (defun eshell-remote-command (command args)
  162. "Insert output from a remote COMMAND, using ARGS.
  163. A remote command is something that executes on a different machine.
  164. An external command simply means external to Emacs.
  165. Note that this function is very crude at the moment. It gathers up
  166. all the output from the remote command, and sends it all at once,
  167. causing the user to wonder if anything's really going on..."
  168. (let ((outbuf (generate-new-buffer " *eshell remote output*"))
  169. (errbuf (generate-new-buffer " *eshell remote error*"))
  170. (command (or (file-remote-p command 'localname) command))
  171. (exitcode 1))
  172. (unwind-protect
  173. (progn
  174. (setq exitcode
  175. (shell-command
  176. (mapconcat 'shell-quote-argument
  177. (append (list command) args) " ")
  178. outbuf errbuf))
  179. (eshell-print (with-current-buffer outbuf (buffer-string)))
  180. (eshell-error (with-current-buffer errbuf (buffer-string))))
  181. (eshell-close-handles exitcode 'nil)
  182. (kill-buffer outbuf)
  183. (kill-buffer errbuf))))
  184. (defun eshell-external-command (command args)
  185. "Insert output from an external COMMAND, using ARGS."
  186. (setq args (eshell-stringify-list (eshell-flatten-list args)))
  187. (let ((interp (eshell-find-interpreter
  188. command
  189. args
  190. ;; `eshell-find-interpreter' does not work correctly
  191. ;; for Tramp file name syntax. But we don't need to
  192. ;; know the interpreter in that case, therefore the
  193. ;; check is suppressed.
  194. (or (and (stringp command) (file-remote-p command))
  195. (file-remote-p default-directory)))))
  196. (cl-assert interp)
  197. (if (functionp (car interp))
  198. (apply (car interp) (append (cdr interp) args))
  199. (eshell-gather-process-output
  200. (car interp) (append (cdr interp) args)))))
  201. (defun eshell/addpath (&rest args)
  202. "Add a set of paths to PATH."
  203. (eshell-eval-using-options
  204. "addpath" args
  205. '((?b "begin" nil prepend "add path element at beginning")
  206. (?h "help" nil nil "display this usage message")
  207. :usage "[-b] PATH
  208. Adds the given PATH to $PATH.")
  209. (if args
  210. (progn
  211. (setq eshell-path-env (getenv "PATH")
  212. args (mapconcat 'identity args path-separator)
  213. eshell-path-env
  214. (if prepend
  215. (concat args path-separator eshell-path-env)
  216. (concat eshell-path-env path-separator args)))
  217. (setenv "PATH" eshell-path-env))
  218. (dolist (dir (parse-colon-path (getenv "PATH")))
  219. (eshell-printn dir)))))
  220. (put 'eshell/addpath 'eshell-no-numeric-conversions t)
  221. (defun eshell-script-interpreter (file)
  222. "Extract the script to run from FILE, if it has #!<interp> in it.
  223. Return nil, or a list of the form:
  224. (INTERPRETER [ARGS] FILE)"
  225. (let ((maxlen eshell-command-interpreter-max-length))
  226. (if (and (file-readable-p file)
  227. (file-regular-p file))
  228. (with-temp-buffer
  229. (insert-file-contents-literally file nil 0 maxlen)
  230. (if (looking-at "#![ \t]*\\([^ \r\t\n]+\\)\\([ \t]+\\(.+\\)\\)?")
  231. (if (match-string 3)
  232. (list (match-string 1)
  233. (match-string 3)
  234. file)
  235. (list (match-string 1)
  236. file)))))))
  237. (defun eshell-find-interpreter (file args &optional no-examine-p)
  238. "Find the command interpreter with which to execute FILE.
  239. If NO-EXAMINE-P is non-nil, FILE will not be inspected for a script
  240. line of the form #!<interp>."
  241. (let ((finterp
  242. (catch 'found
  243. (ignore
  244. (dolist (possible eshell-interpreter-alist)
  245. (cond
  246. ((functionp (car possible))
  247. (let ((fn (car possible)))
  248. (and (funcall fn file args)
  249. (throw 'found (cdr possible)))))
  250. ((stringp (car possible))
  251. (and (string-match (car possible) file)
  252. (throw 'found (cdr possible))))
  253. (t
  254. (error "Invalid interpreter-alist test"))))))))
  255. (if finterp ; first check
  256. (list finterp file)
  257. (let ((fullname (if (file-name-directory file) file
  258. (eshell-search-path file)))
  259. (suffixes eshell-binary-suffixes))
  260. (if (and fullname
  261. (not (file-remote-p fullname))
  262. (file-remote-p default-directory))
  263. (setq fullname (expand-file-name
  264. (concat "./" fullname) default-directory)))
  265. (if (and fullname (not (or eshell-force-execution
  266. (file-executable-p fullname))))
  267. (while suffixes
  268. (let ((try (concat fullname (car suffixes))))
  269. (if (or (file-executable-p try)
  270. (and eshell-force-execution
  271. (file-readable-p try)))
  272. (setq fullname try suffixes nil)
  273. (setq suffixes (cdr suffixes))))))
  274. (cond ((not (and fullname (file-exists-p fullname)))
  275. (let ((name (or fullname file)))
  276. (unless (setq fullname
  277. (run-hook-with-args-until-success
  278. 'eshell-alternate-command-hook file))
  279. (error "%s: command not found" name))))
  280. ((not (or eshell-force-execution
  281. (file-executable-p fullname)))
  282. (error "%s: Permission denied" fullname)))
  283. (let (interp)
  284. (unless no-examine-p
  285. (setq interp (eshell-script-interpreter fullname))
  286. (if interp
  287. (setq interp
  288. (cons (car (eshell-find-interpreter (car interp) args t))
  289. (cdr interp)))))
  290. (or interp (list fullname)))))))
  291. ;;; esh-ext.el ends here