Created
December 15, 2019 19:13
-
-
Save clyfe/6d281e166f46aa9122fadea71b4ab07f to your computer and use it in GitHub Desktop.
Module system extracted from Duct Core, in cljc source.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns modulant.core | |
"Make a system out of a system: `(md/exec modules-map) ;; => system-map`." | |
(:require | |
[clojure.walk :as walk] | |
[integrant.core :as ig] | |
[modulant.merge :as merge])) | |
;;; Logic | |
(defn- fold | |
"Fold modules into an Integrant config map, by calling each module's fn." | |
;; TODO assert map operations are additive | |
[system] (ig/fold system (fn [m _ f] (f m)) {})) | |
(defn build | |
"Turn a Modulant config map into an Integrant config map." | |
[config] (-> config ig/prep ig/init fold)) | |
(defn prep | |
"Prep a Modulant config map into a prepped Integrant config map." | |
[config] (-> config build ig/prep)) | |
(defn exec | |
"Init a Modulant config map into an initialized Integrant system." | |
[config] (-> config prep ig/init)) | |
;;; InertRef(s) | |
(defrecord InertRef [key]) | |
(defrecord InertRefSet [key]) | |
(defn- deactivate-ref [x] | |
(cond | |
(ig/ref? x) (->InertRef (:key x)) | |
(ig/refset? x) (->InertRefSet (:key x)) | |
:else x)) | |
(defn- activate-ref [x] | |
(cond | |
(instance? InertRef x) (ig/ref (:key x)) | |
(instance? InertRefSet x) (ig/refset (:key x)) | |
:else x)) | |
;;; Merging profiles | |
(defn- expand-ancestor-keys [config base] | |
(reduce-kv | |
(fn [m k v] | |
(if-let [ks (seq (keys (ig/find-derived base k)))] | |
(reduce #(assoc %1 %2 v) m ks) | |
(assoc m k v))) | |
{} | |
config)) | |
(defn- merge-configs* [a b] | |
(merge/meta-merge (expand-ancestor-keys a b) | |
(expand-ancestor-keys b a))) | |
(defn merge-configs | |
"Intelligently merge multiple configurations. Uses meta-merge and will merge | |
configurations in order from left to right. Generic top-level keys are merged | |
into more specific descendants, if the descendants exist." | |
[& configs] | |
(merge/unwrap-all (reduce merge-configs* {} configs))) | |
;;; Integrant | |
(derive :modulant.profile/base :modulant/profile) | |
(defmethod ig/init-key :modulant/const [_ v] v) | |
(defmethod ig/prep-key :modulant/module [_ profile] | |
;; Init profiles before modules | |
(assoc profile ::requires (ig/refset :modulant/profile))) | |
(defmethod ig/prep-key :modulant/profile [k profile] | |
;; InertRef(s) logic allows to disable ig/ref(s) while initializing modules, | |
;; such that we don't mix modules and final system during modules init. | |
(-> (walk/postwalk deactivate-ref profile) | |
;; init base before other profiles | |
(cond-> (not (isa? k :modulant.profile/base)) | |
(assoc ::requires (ig/refset :modulant.profile/base))))) | |
(defmethod ig/init-key :modulant/profile [_ profile] | |
(let [profile (walk/postwalk activate-ref (dissoc profile ::requires))] | |
;; Profiles are modules that merge their maps into the resulting system map. | |
#(merge-configs % profile))) | |
;; TODO is this needed given ig/prep-key :modulant/profile ? | |
(defmethod ig/prep-key :modulant.profile/base [_ profile] | |
(walk/postwalk deactivate-ref profile)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment