From 2f4ffbadc7ebaa4cfdb1b12fa5bccd329e6e268a Mon Sep 17 00:00:00 2001
From: apgwoz <git@apgwoz.com>
Date: Mon, 25 Oct 2010 16:05:33 -0400
Subject: [PATCH] * add validation to command line parser via user given function in cmdspec

---
 .../main/clojure/clojure/contrib/command_line.clj  |   45 ++++++++++++++++----
 1 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/modules/command-line/src/main/clojure/clojure/contrib/command_line.clj b/modules/command-line/src/main/clojure/clojure/contrib/command_line.clj
index 6399c4f..739f806 100644
--- a/modules/command-line/src/main/clojure/clojure/contrib/command_line.clj
+++ b/modules/command-line/src/main/clojure/clojure/contrib/command_line.clj
@@ -14,14 +14,42 @@
     clojure.contrib.command-line
     (:use     (clojure [string :only (join)])))
 
+(defn- validation
+  "Calls a validation function on value, which returns nil or throws
+   an Exception when invalid, or the coerced value when OK"
+  [spec value]
+  (try
+   (if-let [validate (:validate spec)]
+     (validate value)
+     value)
+   (catch Exception e
+     (throw (Exception. (str "Invalid value for " (:sym spec)))))))
+
+(defn- spec-munge
+  "Splits spec into the argument keys, and the related data for the spec.
+
+   This function also ensures that both a default and a validate function
+   are in the spec, even if one or both are nil"
+  [spec]
+  (let [[syms [& details]] (split-with symbol? spec)
+        [t d v] details
+        cnt (count details)
+        munged (if (and (== cnt 2) (fn? d)) 
+                 [t nil d]
+                 [t d v])]
+    [syms munged]))
+
 (defn make-map [args cmdspec]
   (let [{spec true [rest-sym] false} (group-by vector? cmdspec)
         rest-str (str rest-sym)
-        key-data (into {} (for [[syms [_ default]] (map #(split-with symbol? %)
-                                                        (conj spec '[help? h?]))
+        key-data (into {} (for [[syms [_ default validate]] 
+                                (map #(spec-munge %)
+                                     (conj spec '[help? h?]))
                                 sym syms]
                             [(re-find #"^.*[^?]" (str sym))
-                             {:sym (str (first syms)) :default default}]))
+                             {:sym (str (first syms)) 
+                              :default default
+                              :validate validate}]))
         defaults (into {} (for [[_ {:keys [default sym]}] key-data
                                 :when default]
                             [sym default]))]
@@ -35,10 +63,11 @@
             :else (if-let [found (key-data keybase)]
                     (if (= \? (last (:sym found)))
                       (recur r (assoc cmdmap (:sym found) true))
-                      (recur (next r) (assoc cmdmap (:sym found)
-                                             (if (or (nil? r) (= \- (ffirst r)))
-                                               (:default found)
-                                               (first r)))))
+                      (recur (next r) 
+                             (assoc cmdmap (:sym found)
+                                    (if (or (nil? r) (= \- (ffirst r)))
+                                      (:default found)
+                                      (validation found (first r))))))
                     (throw (Exception. (str "Unknown option " argkey))))))
         cmdmap))))
 
@@ -71,7 +100,7 @@
   (println 
      (apply align [:l :l :l] 
         (for [spec (:cmdspec cmdmap) :when (vector? spec)]
-            (let [[argnames [text default]] (split-with symbol? spec)
+            (let [[argnames [text default _]] (split-with symbol? spec)
                   [_ opt q] (re-find #"^(.*[^?])(\??)$"
                                  (str (first argnames)))
                   argnames  (map (comp rmv-q str) argnames)
-- 
1.6.1

