duct-framework / docs

Documentation for Duct
21 stars 9 forks source link

[PROPOSAL] : alternative (flat) config syntax #9

Closed jimpil closed 2 months ago

jimpil commented 2 months ago

Hi there,

This came up at work, and the more I think about it, the more I think it sounds 'cute' (to say the least), so basically I'm trying to explore how feasible it is. Currently components are declared using a somewhat nested syntax - for example:

:some.ns/foo {:param1 true :param2 "whatever"}

Imagine we could instead write something flatter:

:some.ns.foo/param1 true 
:some.ns.foo/param2 "whatever"

The mechanics of transforming the latter into the former (before integrant sees it) is not that complicated - in fact, I can already do this via a custom duct-module, but (and correct me if i'm wrong), module transformations only apply to the base profile, so I'm not sure how to rewrite any other profile.

So basically, my question is, would you say that a custom module is enough to achieve what I want, or would something like this need be supported in duct itself (e.g. via a :duct.profile.syntax/flavour)?

Many thanks in advance...

jimpil commented 2 months ago

oops - is this the wrong repo?

jimpil commented 2 months ago

ok so kind of answering my own question here, but it turns out a duct-module suffices. Here is what it looks like:

;; duct module allowing for a less nested syntax in the config.
;; expects flat component options like so:
;; :my.foo.api/bar true
;; :my.foo.api/baz "whatever"
(defmethod ig/init-key ::syntax [_ flavour]
  (fn [config]
    (if (not= 'flat flavour)
      config
      (->> config
           (group-by (comp namespace key))
           (into {}
                 (mapcat
                  (fn [[ns-str entries]]
                    (if (= "duct.core" ns-str) ;; don't touch duct-internal keys
                      (into {} entries)
                      (let [parts (str/split ns-str #"\.")]
                        [[(keyword (str/join \. (drop-last parts)) (peek parts))
                         (into {} entries)]])))))))))

;; nothing changes for integrant
(defmethod ig/init-key :my.foo/api [_ opts] (doto opts println))

Here is how the config looks after being prepped:

{:duct.core/project-ns duct-flat,  ;; as usual
 :duct.core/environment :production,
 :duct-flat.foo/api #:duct-flat.foo.api{:bar true, :baz whatever, :baba {}}} ;; notice the namespaced opts

Hope that helps someone...