juxt / edge

A Clojure application foundation from JUXT
https://juxt.pro/edge/
MIT License
503 stars 62 forks source link

Typing full namespaces for keywords in config.edn is tedious #71

Open SevereOverfl0w opened 5 years ago

SevereOverfl0w commented 5 years ago

It might be worth creating some sugar for this, or maybe it's not so bad :).

SevereOverfl0w commented 5 years ago

A patch to add support for aliases into the config, there's 3 kinds:

Aliases and prefixes have overlapping functionality. Alias functionality can be replicated with prefixes. This patch needs more work to specify an internally consistent format for the aliases.

From 980ad65006f6e45acaa33313c0b6fd557c6ca09f Mon Sep 17 00:00:00 2001
From: Dominic Monroe <dmc@juxt.pro>
Date: Sun, 7 Apr 2019 08:05:11 +0100
Subject: [PATCH] Add aliases to config

---
 lib/edge.system/src/edge/system.clj | 113 +++++++++++++++++++++++++++-
 1 file changed, 112 insertions(+), 1 deletion(-)

diff --git a/lib/edge.system/src/edge/system.clj b/lib/edge.system/src/edge/system.clj
index bff5043..d2af043 100644
--- a/lib/edge.system/src/edge/system.clj
+++ b/lib/edge.system/src/edge/system.clj
@@ -5,6 +5,8 @@
   (:require
    [aero.core :as aero]
    [clojure.java.io :as io]
+   [clojure.walk :as walk]
+   [clojure.string :as string]
    [integrant.core :as ig]))

 ;; There will be integrant tags in our Aero configuration. We need to
@@ -18,12 +20,121 @@
     (locking lock
       (ig/load-namespaces system-config))))

+(defprotocol EdgeNamed
+  (get-namespace [this])
+  (set-namespace [this ns]))
+
+(extend-protocol EdgeNamed
+  clojure.lang.Keyword
+  (get-namespace [k] (namespace k))
+  (set-namespace [k ns] (keyword ns (name k)))
+
+  clojure.lang.Symbol
+  (get-namespace [s] (namespace s))
+  (set-namespace [s ns] (symbol ns (name s))))
+
+(defn- split-ns
+  [ns]
+  (string/split ns #"\."))
+
+(defn- join-ns-segments
+  [ns-segments]
+  (string/join "." ns-segments))
+
+(defn- ns-prefixes
+  [ns]
+  (->> ns
+       split-ns
+       (iterate butlast)
+       (take-while some?)
+       (map join-ns-segments)))
+
+(defn- expand-named
+  [context named]
+  (try
+    (let [named-ns (get-namespace named)]
+      (cond
+        ;; There's a named alias for this key
+        (get-in context [:alias named])
+        (get-in context [:alias named])
+
+        ;; There's an alias for the namespace
+        (get-in context [:require-as (keyword named-ns)])
+        (set-namespace named
+                       (name (get-in context [:require-as (keyword named-ns)])))
+
+        ;; There's a prefix for the namespace
+        (some->> named-ns ns-prefixes (map keyword) (some (:ns-prefix context {})))
+        (let [[prefix replacement]
+              (some #(find (:ns-prefix context) %)
+                    (map keyword (ns-prefixes named-ns)))]
+          (set-namespace
+            named
+            (string/replace-first named-ns
+                                  (name prefix)
+                                  (name replacement))))
+        :else named))
+    (catch Exception e
+      (prn {:context context
+            :named named})
+      (prn (some->> (get-namespace named) ns-prefixes (map keyword)))
+      (throw (ex-info "Error during expansion"
+                      {:context context
+                       :named named}
+                      e)))))
+
+(comment
+  (def ctx {:alias {:foo :edge.system/foo}
+            :require-as {:foo :edge.system.foo}
+            :ns-prefix {:foo :edge.system.bar}})
+
+  (expand-named ctx :foo.baz/abc) ; => :edge.system.bar.baz/abc
+  (expand-named ctx :foo) ; => :edge.system/foo
+  (expand-named ctx :foo/bar) ; => :edge.system.foo/bar
+  )
+
+(defn- expand-config
+  [config]
+  (let [context (::context config)]
+    (walk/postwalk
+      (fn [x]
+        (if (satisfies? EdgeNamed x)
+          (expand-named context x)
+          x))
+      config)))
+
+(comment
+  (expand-config
+    {::context
+     {:ns-prefix {:f :falcon
+                  :fn :falcon.fakenews}}
+
+     :ig/system
+     {:f.pg/embedded nil
+      :fn.foo/article-route {:fn.http.section/article-page 10}}})
+  ;; =>
+  ;{:ig/system {:falcon.pg/embedded nil
+  ;             :falcon.fakenews.foo/article-route
+  ;             {:falcon.fakenews.http.section/article-page 10}}}                                
+  )
+
+(defn- read-config
+  [source given-opts]
+  (let [opts (merge aero/default-opts given-opts {:source source})
+        tag-fn (partial aero/reader opts)
+        wrapped-config (aero/read-config-into-tag-wrapper source)]
+    (-> wrapped-config
+        expand-config
+        (aero/resolve-refs tag-fn)
+        (#'aero/resolve-tag-wrappers tag-fn)
+        (#'aero/realize-deferreds))))
+
 (defn config
   "Read EDN config, with the given aero options. See Aero docs at
   https://github.com/juxt/aero for details."
   [opts]
   (-> (io/resource "config.edn") ;; <1>
-      (aero/read-config opts)) ;; <2>
+      (read-config opts)) ;; <2>
   )

 (defn system-config
-- 
2.21.0