165 lines
4 KiB
Clojure
165 lines
4 KiB
Clojure
(ns ydn.core
|
|
"ydn provide the mechanism to introduce self-references that are "
|
|
(:require [clojure.edn :as edn]
|
|
[clojure.string :as string]
|
|
[clojure.walk :refer [prewalk]])
|
|
(:refer-clojure :exclude [read read-string]))
|
|
|
|
(def keyword-re-str
|
|
":ref([.][^/\\s}]*/|/)[^/.\\s}]*")
|
|
|
|
(def keyword-re
|
|
(re-pattern (str "^" keyword-re-str "$")))
|
|
|
|
(defn ref-kw
|
|
"returns nil if the string does not matches a valid ref keyword format."
|
|
[s]
|
|
(first (re-matches keyword-re s)))
|
|
|
|
(defn key-to-path [kw]
|
|
(some-> kw
|
|
str
|
|
(string/replace #"^:ref[./]?" "")
|
|
(string/split #"[./]")
|
|
(->> (mapv keyword))))
|
|
|
|
(defn get-in! [m p]
|
|
(or (get-in m p)
|
|
(throw (ex-info (format "self reference not found! (path: %s)" (pr-str p)) {:path p}))))
|
|
|
|
(defn name-or-str [x]
|
|
(if (keyword? x)
|
|
(name x)
|
|
(str x)))
|
|
|
|
(defn to-ref
|
|
[edn s]
|
|
(when (ref-kw s)
|
|
(name-or-str
|
|
(get-in! edn (key-to-path s)))))
|
|
|
|
(def interpolation-re
|
|
(re-pattern
|
|
(str "\\$\\{(" keyword-re-str ")\\}")))
|
|
|
|
(defn apply-interpolation
|
|
[s edn]
|
|
(string/replace s
|
|
interpolation-re
|
|
(fn [m] (or (to-ref edn (second m))
|
|
(first m)))))
|
|
|
|
(defn root-namespace [kw]
|
|
(some-> kw namespace (string/split #"[.]") first))
|
|
|
|
(defn apply-self-ref
|
|
[val edn]
|
|
(cond
|
|
(and (keyword? val)
|
|
(contains? #{"ref"} (root-namespace val)))
|
|
(get-in! edn (key-to-path val))
|
|
|
|
(string? val) (apply-interpolation val edn)
|
|
:else val))
|
|
|
|
(defn apply-self-refs
|
|
[edn]
|
|
(prewalk #(apply-self-ref % edn) edn))
|
|
|
|
(defn fix-apply-self-refs
|
|
[ydn-fix-point-limit edn]
|
|
(reduce #(if (= %1 %2)
|
|
(reduced %1)
|
|
(if (= ::error %2)
|
|
(throw (ex-info
|
|
(format "Too many loops! (%d) You probably have a dependency cycle in your config."
|
|
ydn-fix-point-limit)
|
|
{:latest %1}))
|
|
%2))
|
|
(concat
|
|
(take ydn-fix-point-limit (iterate apply-self-refs edn))
|
|
[::error])))
|
|
|
|
(defn extract-refs
|
|
[s]
|
|
(let [matcher (re-matcher interpolation-re s)]
|
|
(->> (repeatedly #(re-find matcher))
|
|
(take-while some?)
|
|
(mapv (comp key-to-path second)))))
|
|
|
|
(comment
|
|
(extract-refs "${:ref.pg/scheme}://${:ref.pg/domain}:${:ref.pg/port}")
|
|
(extract-refs "${:ref.pg/scheme}")
|
|
)
|
|
|
|
(defn path-to-kw
|
|
[p]
|
|
(if (> (count p) 1)
|
|
(keyword (string/join "." (mapv name (pop p)))
|
|
(name (last p)))
|
|
(keyword (name (last p)))
|
|
))
|
|
|
|
(defn rec-deps-list
|
|
[prefix-path edn]
|
|
(assert (vector? prefix-path))
|
|
(apply concat
|
|
(for [[k v] edn]
|
|
(cond
|
|
(keyword? v) (when-let [kw (ref-kw (str v))]
|
|
[{(path-to-kw (conj prefix-path k))
|
|
(keyword (string/replace kw #"^:ref[./]" ""))
|
|
}])
|
|
(string? v) (let [targets (extract-refs v)]
|
|
(for [target targets]
|
|
{(path-to-kw (conj prefix-path k))
|
|
(path-to-kw target)}))
|
|
(map? v) (apply concat (rec-deps-list (conj prefix-path k) v))
|
|
:else nil))))
|
|
|
|
(defn deps-list
|
|
[edn]
|
|
(assert (map? edn) "the edn config should be an hash-map")
|
|
(into {}
|
|
(rec-deps-list [] edn)))
|
|
|
|
(comment
|
|
(deps-list {:x :ref/y
|
|
:y :ref/x
|
|
:foo {:bar :ref/x}
|
|
:z "${:ref/x} & ${:ref/y}"})
|
|
|
|
)
|
|
|
|
(defn detect-cycles
|
|
[g]
|
|
:TODO
|
|
)
|
|
|
|
|
|
|
|
(defn read-string-with-conf
|
|
[{:keys [ydn-fix-point-limit] :as _conf} & args]
|
|
(fix-apply-self-refs
|
|
ydn-fix-point-limit
|
|
(apply edn/read-string args)))
|
|
|
|
(defn read-with-conf
|
|
[{:keys [ydn-fix-point-limit] :as _conf} & args]
|
|
(fix-apply-self-refs
|
|
ydn-fix-point-limit
|
|
(apply edn/read args)))
|
|
|
|
(def default-conf
|
|
{:ydn-fix-point-limit 10})
|
|
|
|
(defn read-string
|
|
"like edn/read-string but also apply self-references"
|
|
[& args]
|
|
(apply read-string-with-conf default-conf args))
|
|
|
|
(defn read
|
|
"like edn/read but also post apply self-references."
|
|
[& args]
|
|
(apply read-with-conf default-conf args))
|