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