| 1 | ;;; esh-opt.el --- command options processing |
| 2 | |
| 3 | ;; Copyright (C) 1999-2012 Free Software Foundation, Inc. |
| 4 | |
| 5 | ;; Author: John Wiegley <johnw@gnu.org> |
| 6 | |
| 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 3 of the License, or |
| 12 | ;; (at your option) 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. If not, see <http://www.gnu.org/licenses/>. |
| 21 | |
| 22 | ;;; Commentary: |
| 23 | |
| 24 | ;;; Code: |
| 25 | |
| 26 | (provide 'esh-opt) |
| 27 | |
| 28 | (eval-when-compile (require 'esh-ext)) |
| 29 | |
| 30 | (defgroup eshell-opt nil |
| 31 | "The options processing code handles command argument parsing for |
| 32 | Eshell commands implemented in Lisp." |
| 33 | :tag "Command options processing" |
| 34 | :group 'eshell) |
| 35 | |
| 36 | ;;; User Functions: |
| 37 | |
| 38 | (defmacro eshell-eval-using-options (name macro-args options &rest body-forms) |
| 39 | "Process NAME's MACRO-ARGS using a set of command line OPTIONS. |
| 40 | After doing so, stores settings in local symbols as declared by OPTIONS; |
| 41 | then evaluates BODY-FORMS -- assuming all was OK. |
| 42 | |
| 43 | OPTIONS is a list, beginning with one or more elements of the form: |
| 44 | \(SHORT LONG VALUE SYMBOL HELP-STRING) |
| 45 | Each of these elements represents a particular command-line switch. |
| 46 | |
| 47 | SHORT is either nil, or a character that can be used as a switch -SHORT. |
| 48 | LONG is either nil, or a string that can be used as a switch --LONG. |
| 49 | At least one of SHORT and LONG must be non-nil. |
| 50 | VALUE is the value associated with the option. It can be either: |
| 51 | t - the option needs a value to be specified after the switch; |
| 52 | nil - the option is given the value t; |
| 53 | anything else - specifies the actual value for the option. |
| 54 | SYMBOL is either nil, or the name of the Lisp symbol that will be bound |
| 55 | to VALUE. A nil SYMBOL calls `eshell-show-usage', and so is appropriate |
| 56 | for a \"--help\" type option. |
| 57 | HELP-STRING is a documentation string for the option. |
| 58 | |
| 59 | Any remaining elements of OPTIONS are :KEYWORD arguments. Some take |
| 60 | arguments, some do not. The recognized :KEYWORDS are: |
| 61 | |
| 62 | :external STRING |
| 63 | STRING is an external command to run if there are unknown switches. |
| 64 | |
| 65 | :usage STRING |
| 66 | STRING is the initial part of the command's documentation string. |
| 67 | It appears before the options are listed. |
| 68 | |
| 69 | :post-usage STRING |
| 70 | STRING is an optional trailing part of the command's documentation string. |
| 71 | It appears after the options, but before the final part of the |
| 72 | documentation about the associated external command (if there is one). |
| 73 | |
| 74 | :show-usage |
| 75 | If present, then show the usage message if the command is called with no |
| 76 | arguments. |
| 77 | |
| 78 | :preserve-args |
| 79 | If present, do not pass MACRO-ARGS through `eshell-flatten-list' |
| 80 | and `eshell-stringify-list'. |
| 81 | |
| 82 | For example, OPTIONS might look like: |
| 83 | |
| 84 | '((?C nil nil multi-column \"multi-column display\") |
| 85 | (nil \"help\" nil nil \"show this usage display\") |
| 86 | (?r \"reverse\" nil reverse-list \"reverse order while sorting\") |
| 87 | :external \"ls\" |
| 88 | :usage \"[OPTION]... [FILE]... |
| 89 | List information about the FILEs (the current directory by default). |
| 90 | Sort entries alphabetically across.\") |
| 91 | |
| 92 | `eshell-eval-using-options' returns the value of the last form in |
| 93 | BODY-FORMS. If instead an external command is run (because of |
| 94 | an unknown option), the tag `eshell-external' will be thrown with |
| 95 | the new process for its value. |
| 96 | |
| 97 | Lastly, any remaining arguments will be available in a locally |
| 98 | interned variable `args' (created using a `let' form)." |
| 99 | (declare (debug (form form sexp body))) |
| 100 | `(let ((temp-args |
| 101 | ,(if (memq ':preserve-args (cadr options)) |
| 102 | macro-args |
| 103 | (list 'eshell-stringify-list |
| 104 | (list 'eshell-flatten-list macro-args))))) |
| 105 | (let ,(append (delq nil (mapcar (lambda (opt) |
| 106 | (and (listp opt) (nth 3 opt))) |
| 107 | (cadr options))) |
| 108 | '(usage-msg last-value ext-command args)) |
| 109 | (eshell-do-opt ,name ,options (quote ,body-forms))))) |
| 110 | |
| 111 | ;;; Internal Functions: |
| 112 | |
| 113 | (defvar temp-args) |
| 114 | (defvar last-value) |
| 115 | (defvar usage-msg) |
| 116 | (defvar ext-command) |
| 117 | ;; Documented part of the interface; see eshell-eval-using-options. |
| 118 | (defvar args) |
| 119 | |
| 120 | (defun eshell-do-opt (name options body-forms) |
| 121 | "Helper function for `eshell-eval-using-options'. |
| 122 | This code doesn't really need to be macro expanded everywhere." |
| 123 | (setq args temp-args) |
| 124 | (if (setq |
| 125 | ext-command |
| 126 | (catch 'eshell-ext-command |
| 127 | (when (setq |
| 128 | usage-msg |
| 129 | (catch 'eshell-usage |
| 130 | (setq last-value nil) |
| 131 | (if (and (= (length args) 0) |
| 132 | (memq ':show-usage options)) |
| 133 | (throw 'eshell-usage |
| 134 | (eshell-show-usage name options))) |
| 135 | (setq args (eshell-process-args name args options) |
| 136 | last-value (eval (append (list 'progn) |
| 137 | body-forms))) |
| 138 | nil)) |
| 139 | (error "%s" usage-msg)))) |
| 140 | (throw 'eshell-external |
| 141 | (eshell-external-command ext-command args)) |
| 142 | last-value)) |
| 143 | |
| 144 | (defun eshell-show-usage (name options) |
| 145 | "Display the usage message for NAME, using OPTIONS." |
| 146 | (let ((usage (format "usage: %s %s\n\n" name |
| 147 | (cadr (memq ':usage options)))) |
| 148 | (extcmd (memq ':external options)) |
| 149 | (post-usage (memq ':post-usage options)) |
| 150 | had-option) |
| 151 | (while options |
| 152 | (when (listp (car options)) |
| 153 | (let ((opt (car options))) |
| 154 | (setq had-option t) |
| 155 | (cond ((and (nth 0 opt) |
| 156 | (nth 1 opt)) |
| 157 | (setq usage |
| 158 | (concat usage |
| 159 | (format " %-20s %s\n" |
| 160 | (format "-%c, --%s" (nth 0 opt) |
| 161 | (nth 1 opt)) |
| 162 | (nth 4 opt))))) |
| 163 | ((nth 0 opt) |
| 164 | (setq usage |
| 165 | (concat usage |
| 166 | (format " %-20s %s\n" |
| 167 | (format "-%c" (nth 0 opt)) |
| 168 | (nth 4 opt))))) |
| 169 | ((nth 1 opt) |
| 170 | (setq usage |
| 171 | (concat usage |
| 172 | (format " %-20s %s\n" |
| 173 | (format " --%s" (nth 1 opt)) |
| 174 | (nth 4 opt))))) |
| 175 | (t (setq had-option nil))))) |
| 176 | (setq options (cdr options))) |
| 177 | (if post-usage |
| 178 | (setq usage (concat usage (and had-option "\n") |
| 179 | (cadr post-usage)))) |
| 180 | (when extcmd |
| 181 | (setq extcmd (eshell-search-path (cadr extcmd))) |
| 182 | (if extcmd |
| 183 | (setq usage |
| 184 | (concat usage |
| 185 | (format " |
| 186 | This command is implemented in Lisp. If an unrecognized option is |
| 187 | passed to this command, the external version '%s' |
| 188 | will be called instead." extcmd))))) |
| 189 | (throw 'eshell-usage usage))) |
| 190 | |
| 191 | (defun eshell-set-option (name ai opt options) |
| 192 | "Using NAME's remaining args (index AI), set the OPT within OPTIONS. |
| 193 | If the option consumes an argument for its value, the argument list |
| 194 | will be modified." |
| 195 | (if (not (nth 3 opt)) |
| 196 | (eshell-show-usage name options) |
| 197 | (if (eq (nth 2 opt) t) |
| 198 | (if (> ai (length args)) |
| 199 | (error "%s: missing option argument" name) |
| 200 | (set (nth 3 opt) (nth ai args)) |
| 201 | (if (> ai 0) |
| 202 | (setcdr (nthcdr (1- ai) args) (nthcdr (1+ ai) args)) |
| 203 | (setq args (cdr args)))) |
| 204 | (set (nth 3 opt) (or (nth 2 opt) t))))) |
| 205 | |
| 206 | (defun eshell-process-option (name switch kind ai options) |
| 207 | "For NAME, process SWITCH (of type KIND), from args at index AI. |
| 208 | The SWITCH will be looked up in the set of OPTIONS. |
| 209 | |
| 210 | SWITCH should be either a string or character. KIND should be the |
| 211 | integer 0 if it's a character, or 1 if it's a string. |
| 212 | |
| 213 | The SWITCH is then be matched against OPTIONS. If no matching handler |
| 214 | is found, and an :external command is defined (and available), it will |
| 215 | be called; otherwise, an error will be triggered to say that the |
| 216 | switch is unrecognized." |
| 217 | (let* ((opts options) |
| 218 | found) |
| 219 | (while opts |
| 220 | (if (and (listp (car opts)) |
| 221 | (nth kind (car opts)) |
| 222 | (if (= kind 0) |
| 223 | (eq switch (nth kind (car opts))) |
| 224 | (string= switch (nth kind (car opts))))) |
| 225 | (progn |
| 226 | (eshell-set-option name ai (car opts) options) |
| 227 | (setq found t opts nil)) |
| 228 | (setq opts (cdr opts)))) |
| 229 | (unless found |
| 230 | (let ((extcmd (memq ':external options))) |
| 231 | (when extcmd |
| 232 | (setq extcmd (eshell-search-path (cadr extcmd))) |
| 233 | (if extcmd |
| 234 | (throw 'eshell-ext-command extcmd) |
| 235 | (if (characterp switch) |
| 236 | (error "%s: unrecognized option -%c" name switch) |
| 237 | (error "%s: unrecognized option --%s" name switch)))))))) |
| 238 | |
| 239 | (defun eshell-process-args (name args options) |
| 240 | "Process the given ARGS using OPTIONS. |
| 241 | This assumes that symbols have been intern'd by `eshell-eval-using-options'." |
| 242 | (let ((ai 0) arg) |
| 243 | (while (< ai (length args)) |
| 244 | (setq arg (nth ai args)) |
| 245 | (if (not (and (stringp arg) |
| 246 | (string-match "^-\\(-\\)?\\(.*\\)" arg))) |
| 247 | (setq ai (1+ ai)) |
| 248 | (let* ((dash (match-string 1 arg)) |
| 249 | (switch (match-string 2 arg))) |
| 250 | (if (= ai 0) |
| 251 | (setq args (cdr args)) |
| 252 | (setcdr (nthcdr (1- ai) args) (nthcdr (1+ ai) args))) |
| 253 | (if dash |
| 254 | (if (> (length switch) 0) |
| 255 | (eshell-process-option name switch 1 ai options) |
| 256 | (setq ai (length args))) |
| 257 | (let ((len (length switch)) |
| 258 | (index 0)) |
| 259 | (while (< index len) |
| 260 | (eshell-process-option name (aref switch index) 0 ai options) |
| 261 | (setq index (1+ index))))))))) |
| 262 | args) |
| 263 | |
| 264 | ;;; esh-opt.el ends here |