Document :initial-value, make it work with :list (by refactoring that).
authorFrancois-Rene Rideau <fare@tunes.org>
Wed, 9 Sep 2009 15:23:40 +0000 (11:23 -0400)
committerFrancois-Rene Rideau <fare@tunes.org>
Wed, 9 Sep 2009 15:23:40 +0000 (11:23 -0400)
.gitignore [new file with mode: 0644]
get-command-line-arguments.lisp
parse-command-line-arguments.lisp

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..df0e0a3
--- /dev/null
@@ -0,0 +1 @@
+*.*f*sl
index 52c4b87..9a8942a 100644 (file)
@@ -8,7 +8,7 @@
 ;;;                                                                  ;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-#+xcvb (module (:depends-on ("cl-launch" "pkgdcl")))
+#+xcvb (module (:depends-on ((:build "cl-launch") "pkgdcl")))
 
 (in-package :command-line-arguments)
 
index 372083a..c099ad6 100644 (file)
@@ -47,12 +47,20 @@ or what's currently left of them as they are processed")
 ;;  or a keyword to push on the option plist.
 ;;  default action is to make a keyword from the first name.
 
-;; :list The value is a plist with keywords :initial-contents and :symbol.
+;; :list
+;;  the value is either T or a plist with keywords :initial-contents and :symbol.
 ;;  The :type must be integer or string.
-;;  :symbol is a special variable and :initial-contents is a list.
+;;  :symbol must be a special variable and
+;;  :initial-contents must be a list (defaults to the provided initial-value).
 ;;  While the options are being processed, the special variable is bound to the
 ;;  initial contents, reversed.
-;;  At the end of option processing, the finalizer reverses the list.
+;;  At the end of option processing, the finalizer reverses the list and calls
+;;  the action, once.
+
+;; :initial-value for specifying an initial value to call the action with
+;;  before arguments are parsed. If the action is a keyword (the default)
+;;  or symbol, this will provide you with a default value.
+;;  :initial-value implies and overrides :optional.
 
 ;; TODO: add this feature, useful for verbose flags.
 ;; :count The value is a plist with keywords :initial-value and :symbol.
@@ -60,6 +68,7 @@ or what's currently left of them as they are processed")
 ;;  incremented each time the option is invoked, decremented each time.
 ;;  Alternatively, if the option is given a numeric argument, the counter
 ;;  is set to the provided argument value.
+;; TODO: add negation for lists with initial-value to allow for empty list.
 
 ;; :negation  Creates string called "no-XXX", or "disable-XXX" if the original name
 ;;  is "enable-XXX".
@@ -79,41 +88,66 @@ or what's currently left of them as they are processed")
    P is the hash table of actions.  NAME is the first name of this option, a string
    or a character.  The keywords are option options for this option specification."
 
-  (let* ((actual-action
-          ;; This is usually the same as ACTION, but if ACTION is #'FOO,
-          ;; then it's the symbol-function of FOO, and if no action is
-          ;; provided, it's a keyword named NAME.
-          (cond
-            ((and (consp action) (eq 'function (car action))
-                  (consp (cdr action)) (null (cddr action)))
-             (symbol-function (cadr action)))
-            (actionp
-             action)
-            (t
-             (intern (string-upcase name) :keyword))))
-         (option-action
-          ;; If the :LIST option is not specified, just return the actual-action.
-          (if list
-              (destructuring-bind (&key initial-contents (symbol (gensym (string-upcase name))))
-                  (and (listp list) list)
-                (let ((final-action #'(lambda ()
-                                        (let ((value (symbol-value symbol)))
-                                          (unless (or optional value)
-                                            (error "No option ~A defined" (option-name name)))
-                                          (command-line-action actual-action (reverse value))))))
-                  (push symbol (gethash :local-symbols p))
-                  (push (reverse initial-contents) (gethash :local-values p))
-                  (push final-action (gethash :finalizers p))
-                  #'(lambda (value)
-                      (case value
-                        ((nil) (set symbol nil))
-                        ((t)   (error "Option ~A requires a parameter" (option-name name)))
-                        (otherwise (push value (symbol-value symbol)))))))
-              actual-action)))
+  (let* ((actual-action (apply #'actual-action-from-spec name
+                               (when actionp (list :action action)))))
     (when initial-value-p
-      (push (list actual-action initial-value) (gethash :initializers p)))
-    option-action))
+      (setf optional t)
+      (push (list 'command-line-action actual-action initial-value) (gethash :initializers p)))
+    ;; If the :LIST option is not specified, just return the actual-action.
+    (if (not list)
+        actual-action
+        (destructuring-bind (&key (initial-contents initial-value)
+                                  (symbol (gensym (string-upcase name))))
+            (and (listp list) list)
+          (push symbol (gethash :local-symbols p))
+          (push (reverse initial-contents) (gethash :local-values p))
+          (flet ((register-finalizer ()
+              (pushnew (list 'finalize-list name symbol optional actual-action)
+                       (gethash :finalizers p)
+                       :test 'equal)))
+            (unless optional
+              (register-finalizer))
+            #'(lambda (value)
+                (when optional
+                  (register-finalizer))
+                (case value
+                  ((nil) (set symbol nil))
+                  ((t)   (error "Option ~A requires a parameter" (option-name name)))
+                  (otherwise (push value (symbol-value symbol))))))))))
+
+(defun finalize-list (name symbol optional actual-action)
+  (let ((value (symbol-value symbol)))
+    (unless (or optional value)
+      (error "No option ~A defined" (option-name name)))
+    (command-line-action actual-action (reverse value))))
+
+(defun actual-action-from-spec (name &key (action nil actionp))
+  ;; If ACTION is not provided, it's a keyword named NAME.
+  ;; If ACTION is provided, and this action is a function, nil, a keyword
+  ;; or other symbol, then it's ACTION.
+  ;; If ACTION is provided and is a list or the form (FUNCTION FOO)
+  ;; (as e.g. read by #'FOO) then it's the symbol-function of FOO.
+  ;; Otherwise, it's an error.
+  ;; See COMMAND-LINE-ACTION below for how to interpret the results.
+  (cond
+    ((not actionp)
+     (intern (string-upcase name) :keyword))
+    ((or (functionp action) (symbolp action))
+     ;; (keywordp action) and (null action) are implicitly included by symbolp
+     action)
+    ((and (consp action) (eq 'function (car action))
+          (consp (cdr action)) (null (cddr action)))
+     (symbol-function (cadr action)))
+    (t
+     (error "Invalid action spec ~S for option ~S" action name))))
 
+(defun command-line-action (action &optional value)
+  (etypecase action
+    (null nil)
+    (keyword  (setf *command-line-options*
+                   (list* action value *command-line-options*)))
+    (symbol   (set action value))
+    (function (funcall action value))))
 
 (defun prepare-command-line-options-specification (specification)
 
@@ -132,9 +166,7 @@ or what's currently left of them as they are processed")
              spec
            (declare (ignorable action initial-value documentation negation-documentation))
            (when initial-value-p
-             (setf optional t)
-             (when list
-               (error "Invalid option spec ~S: can't be a list and have an initial-value" spec)))
+             (setf optional t))
            (when list
              (unless (member type '(integer string))
                (error "option specification ~S wants list but doesn't specify string or integer" spec)))
@@ -172,14 +204,6 @@ or what's currently left of them as they are processed")
   (let ((v (gethash option *command-line-option-specification*)))
     (if v (values t (svref v 0) (svref v 1) (svref v 2)) (values nil nil nil nil))))
 
-(defun command-line-action (action &optional value)
-  (etypecase action
-    (null nil)
-    (keyword  (setf *command-line-options*
-                   (list* action value *command-line-options*)))
-    (symbol   (set action value))
-    (function (funcall action value))))
-
 (defun short-option-p (arg)
 
   "ARG is a string.  Is it like -A, but not --?"
@@ -227,7 +251,7 @@ or what's currently left of them as they are processed")
    error messages."
 
   (flet ((fail ()
-           (error "parameter for option ~A not of type ~A" (option-name option) type)))
+           (error "parameter ~A for option ~A not of type ~A" string (option-name option) type)))
     (ecase type
       ((nil)
        (error "option ~A does not take a parameter" (option-name option)))
@@ -294,8 +318,8 @@ or what's currently left of them as they are processed")
   (progv
       (gethash :local-symbols *command-line-option-specification*)
       (gethash :local-values *command-line-option-specification*)
-    (loop :for (action parameter) :in (gethash :initializers *command-line-option-specification*)
-      :do (command-line-action action parameter))
+    (loop :for (function . parameters) :in (gethash :initializers *command-line-option-specification*)
+      :do (apply function parameters))
     (loop :for arg = (pop *command-line-arguments*) :do
       (cond
        ((or (null arg) (option-end-p arg))
@@ -309,10 +333,10 @@ or what's currently left of them as they are processed")
        ((long-option-p arg)
         (process-long-option arg))
        (t
-         (push arg *command-line-arguments*)
+         (push arg *command-line-arguments*) ; put the first non-option back before we return.
         (return))))
-    (loop :for f :in (gethash :finalizers *command-line-option-specification*)
-          :do (funcall f))))
+    (loop :for (function . parameters) :in (gethash :finalizers *command-line-option-specification*)
+      :do (apply function parameters))))
 
 (defun process-command-line-options (specification command-line)