Repository: juxt/aero
Pull request #7 (2016-03-03 22:50:50)

Extensions
Swapped over the tag reader to be extensible through multimethods. Changed the existing implementation of `env`, `cond` and `hostname` to fit this pattern. 


Patch:
diff --git a/README.md b/README.md
index 5c6dfd2..53ca92c 100644
--- a/README.md
+++ b/README.md
@@ -150,6 +150,18 @@ can specify multiple hostnames in a set.
                     :default 8082}}}
 ```
 
+### Define your own
+
+Aero supports user-defined tag literals. Just extend the `reader` multimethod.
+
+```
+(defmethod reader `mytag
+ [{:keys [profile] :as opts} tag value]
+  (if (= value :favorite)
+     :chocolate
+     :vanilla))
+```
+
 ## Support for Prismatic's schema
 
 A config can be given a :schema entry in the options argument, to specify a schema.
diff --git a/src/aero/core.clj b/src/aero/core.clj
index 5aaf555..00de5a4 100644
--- a/src/aero/core.clj
+++ b/src/aero/core.clj
@@ -8,39 +8,42 @@
    [clojure.java.shell :as sh]
    [schema.core :as s]))
 
-(defn readers [{:keys [profile hostname]}]
-  {'env (fn [x]
-          (cond (vector? x) (or (System/getenv (str (first x))) (second x))
-                :otherwise (System/getenv (str x))))
-   'cond (fn [m]
-           (cond (contains? m profile) (clojure.core/get m profile)
-                 (contains? m :default) (clojure.core/get m :default)
-                 :otherwise nil))
-   'hostname (fn [m]
-               (or
-                (some (fn [[k v]]
-                        (when (or (= k hostname)
-                                  (and (set? k) (contains? k hostname))
-                                  )
-                          v))
-                      m)
-                (get m :default)))})
+(defmulti reader (fn [opts tag value] tag))
+
+(defmethod reader 'env
+  [opts tag value]
+  (cond (vector? value) (or (System/getenv (str (first value))) (second value))
+        :otherwise (System/getenv (str value))))
+
+(defmethod reader 'cond
+  [{:keys [profile]} tag value]
+  (cond (contains? value profile) (clojure.core/get value profile)
+        (contains? value :default) (clojure.core/get value :default)
+        :otherwise nil))
+
+(defmethod reader 'hostname
+  [{:keys [hostname]} tag value]
+  (let [hostn (or hostname (-> (sh/sh "hostname") :out trim))]
+    (or
+     (some (fn [[k v]]
+             (when (or (= k hostn)
+                       (and (set? k) (contains? k hostn)))
+               v))
+           value)
+     (get value :default))))
 
 (defn read-config
   "Optional second argument is a map. Keys are :profile, indicating the
   profile for use with #cond"
-  ([r {:keys [profile schema]}]
-   (let [hostname (-> (sh/sh "hostname") :out trim)
-         config
+  ([r {:keys [schema] :as opts}]
+   (let [config
          (with-open [pr (java.io.PushbackReader. (io/reader r))]
            (edn/read
             {:eof nil
-             :readers (readers {:profile (or profile :default)
-                                :hostname hostname})}
+             :default (partial reader (merge {:profile :default} opts))}
             pr))]
      (when schema
        (s/validate schema config))
      config))
-
   ([r]
    (read-config r {})))


Test patch:
diff --git a/test/aero/config.edn b/test/aero/config.edn
index b5310ca..00d119f 100644
--- a/test/aero/config.edn
+++ b/test/aero/config.edn
@@ -1,1 +1,2 @@
-{:greeting "Hello World!"}
+{:greeting "Hello World!"
+ :flavor #myflavor :favorite }
diff --git a/test/aero/core_test.clj b/test/aero/core_test.clj
index 0a2f116..b175554 100644
--- a/test/aero/core_test.clj
+++ b/test/aero/core_test.clj
@@ -15,19 +15,29 @@
 (deftest hostname-test
   (is (=
        (edn/read
-        {:readers (readers {:profile :default
-                            :hostname "emerald"})}
+        {:default (partial reader {:profile :default
+                                   :hostname "emerald"})}
         (java.io.PushbackReader. (io/reader "test/aero/hosts.edn")))
        {:color "green" :weight 10}))
   (is (=
        (edn/read
-        {:readers (readers {:profile :default
-                            :hostname "granite"})}
+        {:default (partial reader {:profile :default
+                                   :hostname "granite"})}
         (java.io.PushbackReader. (io/reader "test/aero/hosts.edn")))
        {:color "black" :weight nil}))
   (is (=
        (edn/read
-        {:readers (readers {:profile :default
-                            :hostname "diamond"})}
+        {:default (partial reader {:profile :default
+                                   :hostname "diamond"})}
         (java.io.PushbackReader. (io/reader "test/aero/hosts.edn")))
        {:color "white" :weight nil})))
+
+(defmethod reader 'myflavor
+  [opts tag value]
+  (if (= value :favorite)
+    :chocolate
+    :vanilla))
+
+(deftest define-new-type-test
+  (let [config (read-config "test/aero/config.edn")]
+    (is (= :chocolate (:flavor config)))))