Skip to content

Commit

Permalink
weavejester#63 Added refmap construct
Browse files Browse the repository at this point in the history
The refmap construct is similar to refset, but returns a map from keys to
resolved refs.
  • Loading branch information
clyfe committed Dec 28, 2019
1 parent 4078113 commit 786d5b9
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 26 deletions.
84 changes: 59 additions & 25 deletions src/integrant/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,31 @@
[weavejester.dependency :as dep]))

(defprotocol RefLike
(ref-key [r] "Return the key of the reference."))

(defrecord Ref [key] RefLike (ref-key [_] key))
(defrecord RefSet [key] RefLike (ref-key [_] key))
(ref-key [r] "Return the key of the reference.")
(resolve-ref [r config resolvef] "Return the resolved value."))

(declare find-derived)

(defrecord Ref [key]
RefLike
(ref-key [_] key)
(resolve-ref [ref config resolvef]
(let [[k v] (first (find-derived config (ref-key ref)))]
(resolvef k v))))

(defrecord RefSet [key]
RefLike
(ref-key [_] key)
(resolve-ref [refset config resolvef]
(set (for [[k v] (find-derived config (ref-key refset))]
(resolvef k v)))))

(defrecord RefMap [key]
RefLike
(ref-key [_] key)
(resolve-ref [refmap config resolvef]
(into {} (for [[k v] (find-derived config (ref-key refmap))]
[k (resolvef k v)]))))

(defn- composite-key? [keys]
(and (vector? keys) (every? qualified-keyword? keys)))
Expand All @@ -33,6 +54,12 @@
{:pre [(valid-config-key? key)]}
(->RefSet key))

(defn refmap
"Create a map of references to all matching top-level keys in a config map."
[key]
{:pre [(valid-config-key? key)]}
(->RefMap key))

(defn ref?
"Return true if its argument is a ref."
[x]
Expand All @@ -43,8 +70,13 @@
[x]
(instance? RefSet x))

(defn refmap?
"Return true if its argument is a refmap."
[x]
(instance? RefMap x))

(defn reflike?
"Return true if its argument is a ref or a refset."
"Return true if its argument is a ref, refset or a refmap."
[x]
(satisfies? RefLike x))

Expand Down Expand Up @@ -101,22 +133,30 @@
(throw (ambiguous-key-exception m k (map key kvs))))
(first kvs)))

(defn- find-derived-refs [config v include-refsets?]
(->> (depth-search (if include-refsets? reflike? ref?) v)
(map ref-key)
(mapcat #(map key (find-derived config %)))))
(defn- find-derived-refs [config v include-refsets? include-refmaps?]
(let [preds (cond-> #{ref?}
include-refsets? (conj refset?)
include-refmaps? (conj refmap?))
pred (fn [r] (some #(% r) preds))]
(->> (depth-search pred v)
(map ref-key)
(mapcat #(map key (find-derived config %))))))

(defn dependency-graph
"Return a dependency graph of all the refs and refsets in a config. Resolves
derived dependencies. Takes the following options:
`:include-refsets?`
: whether to include refsets in the dependency graph (defaults to true)"
: whether to include refsets in the dependency graph (defaults to true)
`:include-refmaps?`
: whether to include refmaps in the dependency graph (defaults to true)"
([config]
(dependency-graph config {}))
([config {:keys [include-refsets?] :or {include-refsets? true}}]
([config {:keys [include-refsets? include-refmaps?]
:or {include-refsets? true include-refmaps? true}}]
(letfn [(find-refs [v]
(find-derived-refs config v include-refsets?))]
(find-derived-refs config v include-refsets? include-refmaps?))]
(reduce-kv (fn [g k v] (reduce #(dep/depend %1 k %2) g (find-refs v)))
(dep/graph)
config))))
Expand All @@ -129,7 +169,8 @@
(dep/topo-comparator #(compare (str %1) (str %2)) graph))

(defn- find-keys [config keys f]
(let [graph (dependency-graph config {:include-refsets? false})
(let [graph (dependency-graph config {:include-refsets? false
:include-refmaps? false})
keyset (set (mapcat #(map key (find-derived config %)) keys))]
(->> (f graph keyset)
(set/union keyset)
Expand All @@ -142,7 +183,9 @@
(reverse (find-keys config keys dep/transitive-dependents-set)))

#?(:clj
(def ^:private default-readers {'ig/ref ref, 'ig/refset refset}))
(def ^:private default-readers {'ig/ref ref
'ig/refset refset
'ig/refmap refmap}))

#?(:clj
(defn read-string
Expand Down Expand Up @@ -212,20 +255,11 @@
:config config
:key key}))

(defn- resolve-ref [config resolvef ref]
(let [[k v] (first (find-derived config (ref-key ref)))]
(resolvef k v)))

(defn- resolve-refset [config resolvef refset]
(set (for [[k v] (find-derived config (ref-key refset))]
(resolvef k v))))

(defn- expand-key [config resolvef value]
(walk/postwalk
#(cond
(ref? %) (resolve-ref config resolvef %)
(refset? %) (resolve-refset config resolvef %)
:else %)
(reflike? %) (resolve-ref % config resolvef)
:else %)
value))

(defn- run-exception [system completed remaining f k v t]
Expand Down
43 changes: 42 additions & 1 deletion test/integrant/core_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@
(is (ig/reflike? (ig/refset ::foo)))
(is (ig/reflike? (ig/refset [::foo ::bar]))))

(deftest refmap-test
(is (ig/refmap? (ig/refmap ::foo)))
(is (ig/refmap? (ig/refmap [::foo ::bar])))
(is (ig/reflike? (ig/refmap ::foo)))
(is (ig/reflike? (ig/refmap [::foo ::bar]))))

(deftest composite-keyword-test
(let [k (ig/composite-keyword [::a ::b])]
(is (isa? k ::a))
Expand All @@ -86,14 +92,20 @@
(is (= (ig/expand {::a (ig/refset ::ppp), ::p 1, ::pp 2})
{::a #{1 2}, ::p 1, ::pp 2}))
(is (= (ig/expand {::a (ig/refset ::ppp)})
{::a #{}})))
{::a #{}}))
(is (= (ig/expand {::a (ig/refmap ::ppp), ::p 1, ::pp 2})
{::a {::p 1 ::pp 2}, ::p 1, ::pp 2}))
(is (= (ig/expand {::a (ig/refmap ::ppp)})
{::a {}})))

#?(:clj
(deftest read-string-test
(is (= (ig/read-string "{:foo/a #ig/ref :foo/b, :foo/b 1}")
{:foo/a (ig/ref :foo/b), :foo/b 1}))
(is (= (ig/read-string "{:foo/a #ig/refset :foo/b, :foo/b 1}")
{:foo/a (ig/refset :foo/b), :foo/b 1}))
(is (= (ig/read-string "{:foo/a #ig/refmap :foo/b, :foo/b 1}")
{:foo/a (ig/refmap :foo/b), :foo/b 1}))
(is (= (ig/read-string {:readers {'test/var find-var}}
"{:foo/a #test/var clojure.core/+}")
{:foo/a #'+}))))
Expand Down Expand Up @@ -163,6 +175,19 @@

(testing "graph without refsets"
(let [g (ig/dependency-graph m {:include-refsets? false})]
(is (dep/depends? g ::a ::p))
(is (not (dep/depends? g ::b ::p)))
(is (not (dep/depends? g ::b ::pp))))))

(let [m {::a (ig/ref ::p), ::b (ig/refmap ::ppp) ::p 1, ::pp 2}]
(testing "graph with refmaps"
(let [g (ig/dependency-graph m)]
(is (dep/depends? g ::a ::p))
(is (dep/depends? g ::b ::p))
(is (dep/depends? g ::b ::pp))))

(testing "graph without refmaps"
(let [g (ig/dependency-graph m {:include-refmaps? false})]
(is (dep/depends? g ::a ::p))
(is (not (dep/depends? g ::b ::p)))
(is (not (dep/depends? g ::b ::pp)))))))
Expand Down Expand Up @@ -296,6 +321,22 @@
(is (= (ig/init m [::a ::p]) {::a [#{[1]}] ::p [1]}))
(is (= (ig/init m [::a ::pp]) {::a [#{[1] [2]}] ::p [1] ::pp [2]}))))

(testing "with refmaps"
(reset! log [])
(let [m (ig/init {::a (ig/refmap ::ppp), ::p 1, ::pp 2})]
(is (= m {::a [{::p [1] ::pp [2]}], ::p [1], ::pp [2]}))
(is (= @log [[:init ::p 1]
[:init ::pp 2]
[:init ::a {::p [1] ::pp [2]}]]))))

(testing "with refmaps and keys"
(reset! log [])
(let [m {::a (ig/refmap ::ppp), ::p 1, ::pp 2}]
(is (= (ig/init m [::a]) {::a [{}]}))
(is (= (ig/init m [::a ::p]) {::a [{::p [1]}] ::p [1]}))
(is (= (ig/init m [::a ::pp]) {::a [{::p [1] ::pp [2]}]
::p [1] ::pp [2]}))))

(testing "large config"
(is (= (ig/init {:a/a1 {} :a/a2 {:_ (ig/ref :a/a1)}
:a/a3 {} :a/a4 {} :a/a5 {}
Expand Down

0 comments on commit 786d5b9

Please sign in to comment.