From d654135ffebe935317a1f946c123bd25e4fb6aa3 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Thu, 9 Oct 2025 22:05:47 +0300 Subject: Do away with the $ macro for runtime-agnostic purposes --- .gitignore | 1 + resources/clj-kondo/config.edn | 6 +- resources/clj-kondo/hooks/dompa.clj | 16 ++--- src/dompa/nodes.clj | 26 -------- src/dompa/nodes.cljc | 117 ++++++++++++++++++++++++++++++++++++ src/dompa/nodes.cljs | 24 -------- src/dompa/nodes/shared.cljc | 95 ----------------------------- test/dompa/nodes_test.cljc | 6 +- 8 files changed, 131 insertions(+), 160 deletions(-) delete mode 100644 src/dompa/nodes.clj create mode 100644 src/dompa/nodes.cljc delete mode 100644 src/dompa/nodes.cljs delete mode 100644 src/dompa/nodes/shared.cljc diff --git a/.gitignore b/.gitignore index 4f6e667..99d59fb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dompa.iml target/ .lsp/ cljs-test-runner-out/ +.calva/ \ No newline at end of file diff --git a/resources/clj-kondo/config.edn b/resources/clj-kondo/config.edn index 8ee11e4..e6b8708 100644 --- a/resources/clj-kondo/config.edn +++ b/resources/clj-kondo/config.edn @@ -1,5 +1,5 @@ {:hooks {:analyze-call {dompa.nodes/$ hooks.dompa/$ - dompa.nodes.shared/defhtml hooks.dompa/defhtml}} + dompa.nodes/defhtml hooks.dompa/defhtml}} :linters {:dompa.nodes/$-arg-validation {:level :warning} - :dompa.nodes.shared/defhtml-arg-validation {:level :warning}} - :lint-as {dompa.nodes.shared/defhtml clojure.core/defn}} + :dompa.nodes/defhtml-arg-validation {:level :warning}} + :lint-as {dompa.nodes/defhtml clojure.core/defn}} diff --git a/resources/clj-kondo/hooks/dompa.clj b/resources/clj-kondo/hooks/dompa.clj index dac5762..1712a02 100644 --- a/resources/clj-kondo/hooks/dompa.clj +++ b/resources/clj-kondo/hooks/dompa.clj @@ -41,8 +41,8 @@ (doseq [arg (filter #(not (api/list-node? %)) rest-args)] (api/reg-finding! (assoc (meta arg) - :message (str "Invalid argument type. Argument must be a $ macro " - "or a sequence of $ macros.") + :message (str "Invalid argument type. Argument must be a $ node " + "or a sequence of $ nodes.") :type :dompa.nodes/$-arg-validation))) ; if the first arg is a keyword, the second arg is a map, then from @@ -52,8 +52,8 @@ (not (every? #(api/list-node? %) (rest rest-args)))) (api/reg-finding! (assoc (meta (second rest-args)) - :message (str "Invalid argument type. Argument must be a $ macro " - "or a sequence of $ macros.") + :message (str "Invalid argument type. Argument must be a $ node. " + "or a sequence of $ nodes.") :type :dompa.nodes/$-arg-validation))))) (defn defhtml [{:keys [node]}] @@ -64,20 +64,20 @@ (api/reg-finding! (assoc (meta first-arg) :message "Invalid argument type. Binding name must be a symbol." - :type :dompa.templates/defhtml-arg-validation)) + :type :dompa.nodes/defhtml-arg-validation)) ; second argument should be a vector (not (api/vector-node? second-arg)) (api/reg-finding! (assoc (meta second-arg) :message "Invalid argument type. Must be a vector of arguments." - :type :dompa.templates/defhtml-arg-validation)) + :type :dompa.nodes/defhtml-arg-validation)) ; rest of the arguments should be a list (not (every? #(api/list-node? %) rest-args)) (doseq [arg rest-args] (api/reg-finding! (assoc (meta arg) - :message (str "Invalid argument type. Argument must be a $ macro " - "or a sequence of $ macros."))))))) + :message (str "Invalid argument type. Argument must be a $ node " + "or a sequence of $ nodes"))))))) diff --git a/src/dompa/nodes.clj b/src/dompa/nodes.clj deleted file mode 100644 index 53020ba..0000000 --- a/src/dompa/nodes.clj +++ /dev/null @@ -1,26 +0,0 @@ -(ns dompa.nodes) - -(defmacro $ - "Creates a new node. Particularly useful - where you need compile-time composition over run-time, like when - combined with the `defhtml` macro to create HTML string outputs. - - Usage: - - ```clojure - ($ :div - ($ \"hello world\" )) - ```" - [name & opts] - `(if (string? ~name) - {:node/name :dompa/text - :node/value (str ~name ~@opts)} - (let [opts# (list ~@opts) - first-opt# (first opts#) - attrs?# (and (map? first-opt#) - (not (contains? first-opt# :node/name))) - attrs# (if attrs?# first-opt# {}) - children# (if attrs?# (rest opts#) opts#)] - (cond-> {:node/name ~name} - attrs?# (assoc :node/attrs attrs#) - (seq children#) (assoc :node/children children#))))) diff --git a/src/dompa/nodes.cljc b/src/dompa/nodes.cljc new file mode 100644 index 0000000..236271d --- /dev/null +++ b/src/dompa/nodes.cljc @@ -0,0 +1,117 @@ +(ns dompa.nodes) + +(def ^:private default-void-nodes + #{:!doctype :area :base :br :col :embed :hr :img :input + :link :meta :source :track :wbr}) + +(defn- node-attrs-reducer [attrs k v] + (let [attr-name (-> k name)] + (if (true? v) + (str attrs " " attr-name) + (str attrs " " attr-name "=\"" v "\"")))) + +(defn- node->html-reducer-fn + "Returns a reducer function with the initial state of `void-nodes` + set and `nodes->html-fn` function for recursive HTML creation." + [void-nodes nodes->html-fn] + (fn [html node] + (cond + ; fragment nodes expand their children to replace themselves + (and (not (nil? node)) + (= (:node/name node) :<>)) + (str html (nodes->html-fn (:node/children node))) + + ; otherwise business as usual + (not (nil? node)) + (let [node-name (-> node :node/name name) + node-attrs (reduce-kv node-attrs-reducer "" (-> node :node/attrs))] + (cond + (= (-> node :node/name) :dompa/text) + (str html (-> node :node/value)) + + (contains? void-nodes (-> node :node/name)) + (str html "<" node-name node-attrs ">") + + :else + (let [value (nodes->html-fn (-> node :node/children))] + (str html "<" node-name node-attrs ">" value ""))))))) + +(defn traverse + "Recursively traverses given tree of `nodes` with a `traverser-fn` + that gets a single node passed to it and returns a new updated tree. + If the traverses function returns `nil`, the node will be removed. + In any other case the node will be replaced. If you wish to keep + a node unchanged, just return it as-is." + [nodes traverser-fn] + (-> (fn [updated-nodes node] + (if-let [updated-node (traverser-fn node)] + (let [children (traverse (-> updated-node :node/children) traverser-fn)] + (conj updated-nodes (assoc updated-node :node/children children))) + updated-nodes)) + (reduce [] nodes))) + +(defn ->html + "Transform a vector of `nodes` into an HTML string. + + Options: + - `void-nodes` - A set of node names that are self-closing, defaults to: + - `:!doctype` + - `:area` + - `:base` + - `:br` + - `:col` + - `:embed` + - `:hr` + - `:img` + - `:input` + - `:link` + - `:meta` + - `:source` + - `:track` + - `:wbr` + " + ([nodes] + (->html nodes {:void-nodes default-void-nodes})) + ([nodes {:keys [void-nodes]}] + (-> (node->html-reducer-fn void-nodes ->html) + (reduce "" nodes)))) + +(defmacro defhtml + "Creates a new function with `name` that outputs HTML. + + Example usage: + + ```clojure + (defhtml about-page [who] + ($ :div + ($ \"hello \" who))) + + (about-page \"world\") + ``` + " + [name & args-and-elements] + (let [[args & elements] args-and-elements] + `(defn ~name ~args + (->html [~@elements])))) + +(defn $ + "Creates a new node + + Usage: + + ```clojure + ($ :div + ($ \"hello world\" )) + ```" + [name & opts] + (if (string? name) + {:node/name :dompa/text + :node/value (apply str name opts)} + (let [first-opt (first opts) + attrs? (and (map? first-opt) + (not (contains? first-opt :node/name))) + attrs (if attrs? first-opt {}) + children (if attrs? (rest opts) opts)] + (cond-> {:node/name name} + attrs? (assoc :node/attrs attrs) + (seq children) (assoc :node/children children))))) diff --git a/src/dompa/nodes.cljs b/src/dompa/nodes.cljs deleted file mode 100644 index 8e19e2f..0000000 --- a/src/dompa/nodes.cljs +++ /dev/null @@ -1,24 +0,0 @@ -(ns dompa.nodes) - -(defn $ - "Creates a new node - - Usage: - - ```clojure - ($ :div - ($ \"hello world\" )) - ```" - [name & opts] - (if (string? name) - {:node/name :dompa/text - :node/value (apply str name opts)} - (let [first-opt (first opts) - attrs? (and (map? first-opt) - (not (contains? first-opt :node/name))) - attrs (if attrs? first-opt {}) - children (if attrs? (rest opts) opts)] - (cond-> {:node/name name} - attrs? (assoc :node/attrs attrs) - (seq children) (assoc :node/children children))))) - diff --git a/src/dompa/nodes/shared.cljc b/src/dompa/nodes/shared.cljc deleted file mode 100644 index b876048..0000000 --- a/src/dompa/nodes/shared.cljc +++ /dev/null @@ -1,95 +0,0 @@ -(ns dompa.nodes.shared) - -(def ^:private default-void-nodes - #{:!doctype :area :base :br :col :embed :hr :img :input - :link :meta :source :track :wbr}) - -(defn- node-attrs-reducer [attrs k v] - (let [attr-name (-> k name)] - (if (true? v) - (str attrs " " attr-name) - (str attrs " " attr-name "=\"" v "\"")))) - -(defn- node->html-reducer-fn - "Returns a reducer function with the initial state of `void-nodes` - set and `nodes->html-fn` function for recursive HTML creation." - [void-nodes nodes->html-fn] - (fn [html node] - (cond - ; fragment nodes expand their children to replace themselves - (and (not (nil? node)) - (= (:node/name node) :<>)) - (str html (nodes->html-fn (:node/children node))) - - ; otherwise business as usual - (not (nil? node)) - (let [node-name (-> node :node/name name) - node-attrs (reduce-kv node-attrs-reducer "" (-> node :node/attrs))] - (cond - (= (-> node :node/name) :dompa/text) - (str html (-> node :node/value)) - - (contains? void-nodes (-> node :node/name)) - (str html "<" node-name node-attrs">") - - :else - (let [value (nodes->html-fn (-> node :node/children))] - (str html "<" node-name node-attrs ">" value ""))))))) - -(defn traverse - "Recursively traverses given tree of `nodes` with a `traverser-fn` - that gets a single node passed to it and returns a new updated tree. - If the traverses function returns `nil`, the node will be removed. - In any other case the node will be replaced. If you wish to keep - a node unchanged, just return it as-is." - [nodes traverser-fn] - (-> (fn [updated-nodes node] - (if-let [updated-node (traverser-fn node)] - (let [children (traverse (-> updated-node :node/children) traverser-fn)] - (conj updated-nodes (assoc updated-node :node/children children))) - updated-nodes)) - (reduce [] nodes))) - -(defn ->html - "Transform a vector of `nodes` into an HTML string. - - Options: - - `void-nodes` - A set of node names that are self-closing, defaults to: - - `:!doctype` - - `:area` - - `:base` - - `:br` - - `:col` - - `:embed` - - `:hr` - - `:img` - - `:input` - - `:link` - - `:meta` - - `:source` - - `:track` - - `:wbr` - " - ([nodes] - (->html nodes {:void-nodes default-void-nodes})) - ([nodes {:keys [void-nodes]}] - (-> (node->html-reducer-fn void-nodes ->html) - (reduce "" nodes)))) - -(defmacro defhtml - "Creates a new function with `name` that outputs HTML. - - Example usage: - - ```clojure - (defhtml about-page [who] - ($ :div - ($ \"hello \" who))) - - (about-page \"world\") - ``` - " - [name & args-and-elements] - (let [[args & elements] args-and-elements] - `(defn ~name ~args - (->html [~@elements])))) diff --git a/test/dompa/nodes_test.cljc b/test/dompa/nodes_test.cljc index eb86c7a..8d39da1 100644 --- a/test/dompa/nodes_test.cljc +++ b/test/dompa/nodes_test.cljc @@ -1,11 +1,9 @@ (ns dompa.nodes-test #?(:clj (:require [clojure.test :refer [deftest is testing]] - [dompa.nodes :refer [$]] - [dompa.nodes.shared :refer [defhtml traverse ->html]] + [dompa.nodes :refer [$ defhtml traverse ->html]] [dompa.html :as html])) #?(:cljs (:require [cljs.test :refer-macros [deftest testing is]] - [dompa.nodes :refer [$]] - [dompa.nodes.shared :refer [traverse ->html] :refer-macros [defhtml]] + [dompa.nodes :refer [$ traverse ->html] :refer-macros [defhtml]] [dompa.html :as html]))) (defhtml hello [who] -- cgit v1.2.3