29876
|
1 ;;; esh-arg --- argument processing
|
|
2
|
|
3 ;; Copyright (C) 1999, 2000 Free Sofware Foundation
|
|
4
|
|
5 ;; This file is part of GNU Emacs.
|
|
6
|
|
7 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
8 ;; it under the terms of the GNU General Public License as published by
|
|
9 ;; the Free Software Foundation; either version 2, or (at your option)
|
|
10 ;; any later version.
|
|
11
|
|
12 ;; GNU Emacs is distributed in the hope that it will be useful,
|
|
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15 ;; GNU General Public License for more details.
|
|
16
|
|
17 ;; You should have received a copy of the GNU General Public License
|
|
18 ;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
20 ;; Boston, MA 02111-1307, USA.
|
|
21
|
|
22 (provide 'esh-arg)
|
|
23
|
|
24 (eval-when-compile (require 'esh-maint))
|
|
25
|
|
26 (defgroup eshell-arg nil
|
|
27 "Argument parsing involves transforming the arguments passed on the
|
|
28 command line into equivalent Lisp forms that, when evaluated, will
|
|
29 yield the values intended."
|
|
30 :tag "Argument parsing"
|
|
31 :group 'eshell)
|
|
32
|
|
33 ;;; Commentary:
|
|
34
|
|
35 ;; Parsing of arguments can be extended by adding functions to the
|
|
36 ;; hook `eshell-parse-argument-hook'. For a good example of this, see
|
|
37 ;; `eshell-parse-drive-letter', defined in eshell-dirs.el.
|
|
38
|
|
39 (defcustom eshell-parse-argument-hook
|
|
40 (list
|
|
41 ;; a term such as #<buffer NAME>, or #<process NAME> is a buffer
|
|
42 ;; or process reference
|
|
43 'eshell-parse-special-reference
|
|
44
|
|
45 ;; numbers convert to numbers if they stand alone
|
|
46 (function
|
|
47 (lambda ()
|
|
48 (when (and (not eshell-current-argument)
|
|
49 (not eshell-current-quoted)
|
|
50 (looking-at eshell-number-regexp)
|
|
51 (eshell-arg-delimiter (match-end 0)))
|
|
52 (goto-char (match-end 0))
|
|
53 (string-to-number (match-string 0)))))
|
|
54
|
|
55 ;; parse any non-special characters, based on the current context
|
|
56 (function
|
|
57 (lambda ()
|
|
58 (unless eshell-inside-quote-regexp
|
|
59 (setq eshell-inside-quote-regexp
|
|
60 (format "[^%s]+"
|
|
61 (apply 'string eshell-special-chars-inside-quoting))))
|
|
62 (unless eshell-outside-quote-regexp
|
|
63 (setq eshell-outside-quote-regexp
|
|
64 (format "[^%s]+"
|
|
65 (apply 'string eshell-special-chars-outside-quoting))))
|
|
66 (when (looking-at (if eshell-current-quoted
|
|
67 eshell-inside-quote-regexp
|
|
68 eshell-outside-quote-regexp))
|
|
69 (goto-char (match-end 0))
|
|
70 (let ((str (match-string 0)))
|
|
71 (if str
|
|
72 (set-text-properties 0 (length str) nil str))
|
|
73 str))))
|
|
74
|
|
75 ;; whitespace or a comment is an argument delimiter
|
|
76 (function
|
|
77 (lambda ()
|
|
78 (let (comment-p)
|
|
79 (when (or (looking-at "[ \t]+")
|
|
80 (and (not eshell-current-argument)
|
|
81 (looking-at "#\\([^<'].*\\|$\\)")
|
|
82 (setq comment-p t)))
|
|
83 (if comment-p
|
|
84 (add-text-properties (match-beginning 0) (match-end 0)
|
|
85 '(comment t)))
|
|
86 (goto-char (match-end 0))
|
|
87 (eshell-finish-arg)))))
|
|
88
|
|
89 ;; backslash before a special character means escape it
|
|
90 'eshell-parse-backslash
|
|
91
|
|
92 ;; text beginning with ' is a literally quoted
|
|
93 'eshell-parse-literal-quote
|
|
94
|
|
95 ;; text beginning with " is interpolably quoted
|
|
96 'eshell-parse-double-quote
|
|
97
|
|
98 ;; argument delimiter
|
|
99 'eshell-parse-delimiter)
|
|
100 "*Define how to process Eshell command line arguments.
|
|
101 When each function on this hook is called, point will be at the
|
|
102 current position within the argument list. The function should either
|
|
103 return nil, meaning that it did no argument parsing, or it should
|
|
104 return the result of the parse as a sexp. It is also responsible for
|
|
105 moving the point forward to reflect the amount of input text that was
|
|
106 parsed.
|
|
107
|
|
108 If no function handles the current character at point, it will be
|
|
109 treated as a literal character."
|
|
110 :type 'hook
|
|
111 :group 'eshell-arg)
|
|
112
|
|
113 ;;; Code:
|
|
114
|
|
115 ;;; User Variables:
|
|
116
|
|
117 (defcustom eshell-arg-load-hook '(eshell-arg-initialize)
|
|
118 "*A hook that gets run when `eshell-arg' is loaded."
|
|
119 :type 'hook
|
|
120 :group 'eshell-arg)
|
|
121
|
|
122 (defcustom eshell-delimiter-argument-list '(?\; ?& ?\| ?\> ? ?\t ?\n)
|
|
123 "List of characters to recognize as argument separators."
|
|
124 :type '(repeat character)
|
|
125 :group 'eshell-arg)
|
|
126
|
|
127 (defcustom eshell-special-chars-inside-quoting '(?\\ ?\")
|
|
128 "*Characters which are still special inside double quotes."
|
|
129 :type '(repeat character)
|
|
130 :group 'eshell-arg)
|
|
131
|
|
132 (defcustom eshell-special-chars-outside-quoting
|
|
133 (append eshell-delimiter-argument-list '(?# ?! ?\\ ?\" ?\'))
|
|
134 "*Characters that require escaping outside of double quotes.
|
|
135 Without escaping them, they will introduce a change in the argument."
|
|
136 :type '(repeat character)
|
|
137 :group 'eshell-arg)
|
|
138
|
|
139 ;;; Internal Variables:
|
|
140
|
|
141 (defvar eshell-current-argument nil)
|
|
142 (defvar eshell-current-modifiers nil)
|
|
143 (defvar eshell-arg-listified nil)
|
|
144 (defvar eshell-nested-argument nil)
|
|
145 (defvar eshell-current-quoted nil)
|
|
146 (defvar eshell-inside-quote-regexp nil)
|
|
147 (defvar eshell-outside-quote-regexp nil)
|
|
148
|
|
149 ;;; Functions:
|
|
150
|
|
151 (defun eshell-arg-initialize ()
|
|
152 "Initialize the argument parsing code."
|
|
153 (define-key eshell-command-map [(meta ?b)] 'eshell-insert-buffer-name)
|
|
154 (set (make-local-variable 'eshell-inside-quote-regexp) nil)
|
|
155 (set (make-local-variable 'eshell-outside-quote-regexp) nil))
|
|
156
|
|
157 (defun eshell-insert-buffer-name (buffer-name)
|
|
158 "Insert BUFFER-NAME into the current buffer at point."
|
|
159 (interactive "BName of buffer: ")
|
|
160 (insert-and-inherit "#<buffer " buffer-name ">"))
|
|
161
|
|
162 (defsubst eshell-escape-arg (string)
|
|
163 "Return STRING with the `escaped' property on it."
|
|
164 (if (stringp string)
|
|
165 (add-text-properties 0 (length string) '(escaped t) string))
|
|
166 string)
|
|
167
|
|
168 (defun eshell-resolve-current-argument ()
|
|
169 "If there are pending modifications to be made, make them now."
|
|
170 (when eshell-current-argument
|
|
171 (when eshell-arg-listified
|
|
172 (let ((parts eshell-current-argument))
|
|
173 (while parts
|
|
174 (unless (stringp (car parts))
|
|
175 (setcar parts
|
|
176 (list 'eshell-to-flat-string (car parts))))
|
|
177 (setq parts (cdr parts)))
|
|
178 (setq eshell-current-argument
|
|
179 (list 'eshell-convert
|
|
180 (append (list 'concat) eshell-current-argument))))
|
|
181 (setq eshell-arg-listified nil))
|
|
182 (while eshell-current-modifiers
|
|
183 (setq eshell-current-argument
|
|
184 (list (car eshell-current-modifiers) eshell-current-argument)
|
|
185 eshell-current-modifiers (cdr eshell-current-modifiers))))
|
|
186 (setq eshell-current-modifiers nil))
|
|
187
|
|
188 (defun eshell-finish-arg (&optional argument)
|
|
189 "Finish the current argument being processed."
|
|
190 (if argument
|
|
191 (setq eshell-current-argument argument))
|
|
192 (throw 'eshell-arg-done t))
|
|
193
|
|
194 (defsubst eshell-arg-delimiter (&optional pos)
|
|
195 "Return non-nil if POS is an argument delimiter.
|
|
196 If POS is nil, the location of point is checked."
|
|
197 (let ((pos (or pos (point))))
|
|
198 (or (= pos (point-max))
|
|
199 (memq (char-after pos) eshell-delimiter-argument-list))))
|
|
200
|
|
201 ;; Argument parsing
|
|
202
|
|
203 (defun eshell-parse-arguments (beg end)
|
|
204 "Parse all of the arguments at point from BEG to END.
|
|
205 Returns the list of arguments in their raw form.
|
|
206 Point is left at the end of the arguments."
|
|
207 (save-excursion
|
|
208 (save-restriction
|
|
209 (goto-char beg)
|
|
210 (narrow-to-region beg end)
|
|
211 (let ((inhibit-point-motion-hooks t)
|
|
212 (args (list t))
|
|
213 after-change-functions
|
|
214 delim)
|
|
215 (remove-text-properties (point-min) (point-max)
|
|
216 '(arg-begin nil arg-end nil))
|
|
217 (if (setq
|
|
218 delim
|
|
219 (catch 'eshell-incomplete
|
|
220 (while (not (eobp))
|
|
221 (let* ((here (point))
|
|
222 (arg (eshell-parse-argument)))
|
|
223 (if (= (point) here)
|
|
224 (error "Failed to parse argument '%s'"
|
|
225 (buffer-substring here (point-max))))
|
|
226 (and arg (nconc args (list arg)))))))
|
|
227 (if (listp delim)
|
|
228 (throw 'eshell-incomplete delim)
|
|
229 (throw 'eshell-incomplete
|
|
230 (list delim (point) (cdr args)))))
|
|
231 (cdr args)))))
|
|
232
|
|
233 (defun eshell-parse-argument ()
|
|
234 "Get the next argument. Leave point after it."
|
|
235 (let* ((outer (null eshell-nested-argument))
|
|
236 (arg-begin (and outer (point)))
|
|
237 (eshell-nested-argument t)
|
|
238 eshell-current-argument
|
|
239 eshell-current-modifiers
|
|
240 eshell-arg-listified)
|
|
241 (catch 'eshell-arg-done
|
|
242 (while (not (eobp))
|
|
243 (let ((result
|
|
244 (or (run-hook-with-args-until-success
|
|
245 'eshell-parse-argument-hook)
|
|
246 (prog1
|
|
247 (char-to-string (char-after))
|
|
248 (forward-char)))))
|
|
249 (if (not eshell-current-argument)
|
|
250 (setq eshell-current-argument result)
|
|
251 (unless eshell-arg-listified
|
|
252 (setq eshell-current-argument
|
|
253 (list eshell-current-argument)
|
|
254 eshell-arg-listified t))
|
|
255 (nconc eshell-current-argument (list result))))))
|
|
256 (when (and outer eshell-current-argument)
|
|
257 (add-text-properties arg-begin (1+ arg-begin)
|
|
258 '(arg-begin t rear-nonsticky
|
|
259 (arg-begin arg-end)))
|
|
260 (add-text-properties (1- (point)) (point)
|
|
261 '(arg-end t rear-nonsticky
|
|
262 (arg-end arg-begin))))
|
|
263 (eshell-resolve-current-argument)
|
|
264 eshell-current-argument))
|
|
265
|
|
266 (defsubst eshell-operator (&rest args)
|
|
267 "A stub function that generates an error if a floating operator is found."
|
|
268 (error "Unhandled operator in input text"))
|
|
269
|
|
270 (defsubst eshell-looking-at-backslash-return (pos)
|
|
271 "Test whether a backslash-return sequence occurs at POS."
|
|
272 (and (eq (char-after pos) ?\\)
|
|
273 (or (= (1+ pos) (point-max))
|
|
274 (and (eq (char-after (1+ pos)) ?\n)
|
|
275 (= (+ pos 2) (point-max))))))
|
|
276
|
|
277 (defun eshell-quote-backslash (string &optional index)
|
|
278 "Intelligently backslash the character occuring in STRING at INDEX.
|
|
279 If the character is itself a backslash, it needs no escaping."
|
|
280 (let ((char (aref string index)))
|
|
281 (if (eq char ?\\)
|
|
282 (char-to-string char)
|
|
283 (if (memq char eshell-special-chars-outside-quoting)
|
|
284 (string ?\\ char)))))
|
|
285
|
|
286 (defun eshell-parse-backslash ()
|
|
287 "Parse a single backslash (\) character, which might mean escape.
|
|
288 It only means escape if the character immediately following is a
|
|
289 special character that is not itself a backslash."
|
|
290 (when (eq (char-after) ?\\)
|
|
291 (if (eshell-looking-at-backslash-return (point))
|
|
292 (throw 'eshell-incomplete ?\\)
|
|
293 (if (and (not (eq (char-after (1+ (point))) ?\\))
|
|
294 (if eshell-current-quoted
|
|
295 (memq (char-after (1+ (point)))
|
|
296 eshell-special-chars-inside-quoting)
|
|
297 (memq (char-after (1+ (point)))
|
|
298 eshell-special-chars-outside-quoting)))
|
|
299 (progn
|
|
300 (forward-char 2)
|
|
301 (list 'eshell-escape-arg
|
|
302 (char-to-string (char-before))))
|
|
303 ;; allow \\<RET> to mean a literal "\" character followed by a
|
|
304 ;; normal return, rather than a backslash followed by a line
|
|
305 ;; continuator (i.e., "\\ + \n" rather than "\ + \\n"). This
|
|
306 ;; is necessary because backslashes in Eshell are not special
|
|
307 ;; unless they either precede something special, or precede a
|
|
308 ;; backslash that precedes something special. (Mainly this is
|
|
309 ;; done to make using backslash on Windows systems more
|
|
310 ;; natural-feeling).
|
|
311 (if (eshell-looking-at-backslash-return (1+ (point)))
|
|
312 (forward-char))
|
|
313 (forward-char)
|
|
314 "\\"))))
|
|
315
|
|
316 (defun eshell-parse-literal-quote ()
|
|
317 "Parse a literally quoted string. Nothing has special meaning!"
|
|
318 (if (eq (char-after) ?\')
|
|
319 (let ((end (eshell-find-delimiter ?\' ?\')))
|
|
320 (if (not end)
|
|
321 (throw 'eshell-incomplete ?\')
|
|
322 (let ((string (buffer-substring-no-properties (1+ (point)) end)))
|
|
323 (goto-char (1+ end))
|
|
324 (while (string-match "''" string)
|
|
325 (setq string (replace-match "'" t t string)))
|
|
326 (list 'eshell-escape-arg string))))))
|
|
327
|
|
328 (defun eshell-parse-double-quote ()
|
|
329 "Parse a double quoted string, which allows for variable interpolation."
|
|
330 (when (eq (char-after) ?\")
|
|
331 (forward-char)
|
|
332 (let* ((end (eshell-find-delimiter ?\" ?\" nil nil t))
|
|
333 (eshell-current-quoted t))
|
|
334 (if (not end)
|
|
335 (throw 'eshell-incomplete ?\")
|
|
336 (prog1
|
|
337 (save-restriction
|
|
338 (narrow-to-region (point) end)
|
|
339 (list 'eshell-escape-arg
|
|
340 (eshell-parse-argument)))
|
|
341 (goto-char (1+ end)))))))
|
|
342
|
|
343 (defun eshell-parse-special-reference ()
|
|
344 "Parse a special syntax reference, of the form '#<type arg>'."
|
|
345 (if (and (not eshell-current-argument)
|
|
346 (not eshell-current-quoted)
|
|
347 (looking-at "#<\\(buffer\\|process\\)\\s-"))
|
|
348 (let ((here (point)))
|
|
349 (goto-char (match-end 0))
|
|
350 (let* ((buffer-p (string= (match-string 1) "buffer"))
|
|
351 (end (eshell-find-delimiter ?\< ?\>)))
|
|
352 (if (not end)
|
|
353 (throw 'eshell-incomplete ?\<)
|
|
354 (if (eshell-arg-delimiter (1+ end))
|
|
355 (prog1
|
|
356 (list (if buffer-p 'get-buffer-create 'get-process)
|
|
357 (buffer-substring-no-properties (point) end))
|
|
358 (goto-char (1+ end)))
|
|
359 (ignore (goto-char here))))))))
|
|
360
|
|
361 (defun eshell-parse-delimiter ()
|
|
362 "Parse an argument delimiter, which is essentially a command operator."
|
|
363 ;; this `eshell-operator' keyword gets parsed out by
|
|
364 ;; `eshell-separate-commands'. Right now the only possibility for
|
|
365 ;; error is an incorrect output redirection specifier.
|
|
366 (when (looking-at "[&|;\n]\\s-*")
|
|
367 (let ((end (match-end 0)))
|
|
368 (if eshell-current-argument
|
|
369 (eshell-finish-arg)
|
|
370 (eshell-finish-arg
|
|
371 (prog1
|
|
372 (list 'eshell-operator
|
|
373 (cond
|
|
374 ((eq (char-after end) ?\&)
|
|
375 (setq end (1+ end)) "&&")
|
|
376 ((eq (char-after end) ?\|)
|
|
377 (setq end (1+ end)) "||")
|
|
378 ((eq (char-after) ?\n) ";")
|
|
379 (t
|
|
380 (char-to-string (char-after)))))
|
|
381 (goto-char end)))))))
|
|
382
|
|
383 ;;; esh-arg.el ends here
|