Cleanup Eshell to rely less on dynamic scoping.
[bpt/emacs.git] / lisp / eshell / esh-opt.el
index d716240..c62cbc7 100644 (file)
@@ -1,6 +1,6 @@
 ;;; esh-opt.el --- command options processing
 
-;; Copyright (C) 1999-201 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2013 Free Software Foundation, Inc.
 
 ;; Author: John Wiegley <johnw@gnu.org>
 
 
 (provide 'esh-opt)
 
-(eval-when-compile (require 'esh-ext))
+(require 'esh-ext)
 
-(defgroup eshell-opt nil
-  "The options processing code handles command argument parsing for
-Eshell commands implemented in Lisp."
-  :tag "Command options processing"
-  :group 'eshell)
+;; Unused.
+;; (defgroup eshell-opt nil
+;;   "The options processing code handles command argument parsing for
+;; Eshell commands implemented in Lisp."
+;;   :tag "Command options processing"
+;;   :group 'eshell)
 
 ;;; User Functions:
 
-(defmacro eshell-eval-using-options (name macro-args
-                                         options &rest body-forms)
+(defmacro eshell-eval-using-options (name macro-args options &rest body-forms)
   "Process NAME's MACRO-ARGS using a set of command line OPTIONS.
-After doing so, settings will be stored in local symbols as declared
-by OPTIONS; FORMS will then be evaluated -- assuming all was OK.
+After doing so, stores settings in local symbols as declared by OPTIONS;
+then evaluates BODY-FORMS -- assuming all was OK.
 
-The syntax of OPTIONS is:
+OPTIONS is a list, beginning with one or more elements of the form:
+\(SHORT LONG VALUE SYMBOL HELP-STRING)
+Each of these elements represents a particular command-line switch.
+
+SHORT is either nil, or a character that can be used as a switch -SHORT.
+LONG is either nil, or a string that can be used as a switch --LONG.
+At least one of SHORT and LONG must be non-nil.
+VALUE is the value associated with the option.  It can be either:
+  t   - the option needs a value to be specified after the switch;
+  nil - the option is given the value t;
+  anything else - specifies the actual value for the option.
+SYMBOL is either nil, or the name of the Lisp symbol that will be bound
+to VALUE.  A nil SYMBOL calls `eshell-show-usage', and so is appropriate
+for a \"--help\" type option.
+HELP-STRING is a documentation string for the option.
+
+Any remaining elements of OPTIONS are :KEYWORD arguments.  Some take
+arguments, some do not.  The recognized :KEYWORDS are:
+
+:external STRING
+  STRING is an external command to run if there are unknown switches.
+
+:usage STRING
+  STRING is the initial part of the command's documentation string.
+  It appears before the options are listed.
+
+:post-usage STRING
+  STRING is an optional trailing part of the command's documentation string.
+  It appears after the options, but before the final part of the
+  documentation about the associated external command (if there is one).
+
+:show-usage
+  If present, then show the usage message if the command is called with no
+  arguments.
+
+:preserve-args
+  If present, do not pass MACRO-ARGS through `eshell-flatten-list'
+and `eshell-stringify-list'.
+
+For example, OPTIONS might look like:
 
   '((?C  nil         nil multi-column    \"multi-column display\")
     (nil \"help\"      nil nil             \"show this usage display\")
@@ -52,8 +91,9 @@ The syntax of OPTIONS is:
   Sort entries alphabetically across.\")
 
 `eshell-eval-using-options' returns the value of the last form in
-BODY-FORMS.  If instead an external command is run, the tag
-`eshell-external' will be thrown with the new process for its value.
+BODY-FORMS.  If instead an external command is run (because of
+an unknown option), the tag `eshell-external' will be thrown with
+the new process for its value.
 
 Lastly, any remaining arguments will be available in a locally
 interned variable `args' (created using a `let' form)."
@@ -63,31 +103,25 @@ interned variable `args' (created using a `let' form)."
               macro-args
             (list 'eshell-stringify-list
                   (list 'eshell-flatten-list macro-args)))))
-     (let ,(append (mapcar (lambda (opt)
-                            (or (and (listp opt) (nth 3 opt))
-                                'eshell-option-stub))
-                          (cadr options))
-                  '(usage-msg last-value ext-command args))
-       (eshell-do-opt ,name ,options (quote ,body-forms)))))
+     (let ,(delq nil (mapcar (lambda (opt)
+                                      (and (listp opt) (nth 3 opt)))
+                                    (cadr options)))
+       ;; FIXME: `options' ends up hiding some variable names under `quote',
+       ;; which is incompatible with lexical scoping!!
+       (eshell-do-opt ,name ,options (lambda (args) ,@body-forms) temp-args))))
 
 ;;; Internal Functions:
 
-(defvar temp-args)
-(defvar last-value)
-(defvar usage-msg)
-(defvar ext-command)
 ;; Documented part of the interface; see eshell-eval-using-options.
-(defvar args)
+(defvar eshell--args)
 
-(defun eshell-do-opt (name options body-forms)
+(defun eshell-do-opt (name options body-fun args)
   "Helper function for `eshell-eval-using-options'.
 This code doesn't really need to be macro expanded everywhere."
-  (setq args temp-args)
-  (if (setq
-       ext-command
+  (let* (last-value
+         (ext-command
        (catch 'eshell-ext-command
-        (when (setq
-               usage-msg
+            (let ((usage-msg
                (catch 'eshell-usage
                  (setq last-value nil)
                  (if (and (= (length args) 0)
@@ -95,13 +129,14 @@ This code doesn't really need to be macro expanded everywhere."
                      (throw 'eshell-usage
                             (eshell-show-usage name options)))
                  (setq args (eshell-process-args name args options)
-                       last-value (eval (append (list 'progn)
-                                                body-forms)))
-                 nil))
-          (error "%s" usage-msg))))
+                       last-value (funcall body-fun args))
+                     nil)))
+              (when usage-msg
+                (error "%s" usage-msg))))))
+    (if ext-command
       (throw 'eshell-external
              (eshell-external-command ext-command args))
-    last-value))
+      last-value)))
 
 (defun eshell-show-usage (name options)
   "Display the usage message for NAME, using OPTIONS."
@@ -157,12 +192,13 @@ will be modified."
   (if (not (nth 3 opt))
       (eshell-show-usage name options)
     (if (eq (nth 2 opt) t)
-       (if (> ai (length args))
+       (if (> ai (length eshell--args))
            (error "%s: missing option argument" name)
-         (set (nth 3 opt) (nth ai args))
+         (set (nth 3 opt) (nth ai eshell--args))
          (if (> ai 0)
-             (setcdr (nthcdr (1- ai) args) (nthcdr (1+ ai) args))
-           (setq args (cdr args))))
+             (setcdr (nthcdr (1- ai) eshell--args)
+                      (nthcdr (1+ ai) eshell--args))
+           (setq eshell--args (cdr eshell--args))))
       (set (nth 3 opt) (or (nth 2 opt) t)))))
 
 (defun eshell-process-option (name switch kind ai options)
@@ -180,10 +216,8 @@ switch is unrecognized."
         found)
     (while opts
       (if (and (listp (car opts))
-                (nth kind (car opts))
-                (if (= kind 0)
-                    (eq switch (nth kind (car opts)))
-                  (string= switch (nth kind (car opts)))))
+               (nth kind (car opts))
+               (equal switch (nth kind (car opts))))
          (progn
            (eshell-set-option name ai (car opts) options)
            (setq found t opts nil))
@@ -194,14 +228,15 @@ switch is unrecognized."
          (setq extcmd (eshell-search-path (cadr extcmd)))
          (if extcmd
              (throw 'eshell-ext-command extcmd)
-           (if (characterp switch)
-               (error "%s: unrecognized option -%c" name switch)
-             (error "%s: unrecognized option --%s" name switch))))))))
+            (error (if (characterp switch) "%s: unrecognized option -%c"
+                     "%s: unrecognized option --%s")
+                   name switch)))))))
 
 (defun eshell-process-args (name args options)
   "Process the given ARGS using OPTIONS.
-This assumes that symbols have been intern'd by `eshell-with-options'."
-  (let ((ai 0) arg)
+This assumes that symbols have been intern'd by `eshell-eval-using-options'."
+  (let ((ai 0) arg
+        (eshell--args args))
     (while (< ai (length args))
       (setq arg (nth ai args))
       (if (not (and (stringp arg)