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