esh-arg.el 14 KB

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