From 6619a8da60712c36338cc7e0f1cf8a5b8d9dbed7 Mon Sep 17 00:00:00 2001 From: Asko Nomm Date: Sun, 7 Dec 2025 01:12:37 +0200 Subject: [PATCH 1/2] Improve templating ergonomics (#11) --- src/dompa/nodes.cljc | 79 +++++++++++++++++++++++---- test/dompa/coordinates_test.cljc | 30 ++++++----- test/dompa/html_test.cljc | 12 +++-- test/dompa/nodes_test.cljc | 91 +++++++++++++++++++++++++++----- 4 files changed, 173 insertions(+), 39 deletions(-) diff --git a/src/dompa/nodes.cljc b/src/dompa/nodes.cljc index e99b364..3e5c72f 100644 --- a/src/dompa/nodes.cljc +++ b/src/dompa/nodes.cljc @@ -1,5 +1,6 @@ (ns dompa.nodes - (:require [clojure.zip :as zip])) + (:require + [clojure.zip :as zip])) (def ^:private default-void-nodes #{:!doctype :!DOCTYPE :area :base :br :col :embed :hr :img :input @@ -35,7 +36,9 @@ :else (let [value (nodes->html-fn (-> node :node/children))] - (str html "<" node-name node-attrs ">" value ""))))))) + (str html "<" node-name node-attrs ">" value "")))) + + :else ""))) (defn traverse "Recursively traverses given tree of `nodes` with a `traverser-fn` @@ -55,13 +58,13 @@ "Creates a zipper for given a given `node`." [node] (zip/zipper - (fn branch? [node] - (boolean (seq (:node/children node)))) - (fn children [node] - (:node/children node)) - (fn make-node [node children] - (assoc node :node/children children)) - node)) + (fn branch? [node] + (boolean (seq (:node/children node)))) + (fn children [node] + (:node/children node)) + (fn make-node [node children] + (assoc node :node/children children)) + node)) (defn ->html "Transform a vector of `nodes` into an HTML string. @@ -107,7 +110,7 @@ `(defn ~name ~args (->html (vector ~@elements))))) -(defn $ +(defn $$ "Creates a new node Usage: @@ -128,3 +131,59 @@ (cond-> {:node/name name} attrs? (assoc :node/attrs attrs) (seq children) (assoc :node/children (flatten children)))))) + +(defn- nodes-from-opt + [opt] + (cond (map? opt) + opt + + (and (sequential? opt) + (> (count opt) 1)) + {:node/name :<> + :node/children opt} + + (and (sequential? opt) + (= (count opt) 1)) + (first opt) + + :else + {:node/name :dompa/text + :node/value (str opt)})) + +(defn- node-from-opts + [opts] + (let [first-opt (first opts) + second-op (second opts) + attrs? (and (map? second-op) + (not (contains? second-op :node/name))) + attrs (if attrs? second-op {}) + children-opts (if attrs? (drop 2 opts) (rest opts)) + children-nodes (->> children-opts + (map nodes-from-opt) + flatten)] + (cond-> {:node/name first-opt} + attrs? (assoc :node/attrs attrs) + (seq children-nodes) (assoc :node/children children-nodes)))) + +(defn- nodes-from-opts + [opts] + (let [nodes (->> (remove nil? opts) + (map nodes-from-opt))] + (if (> (count nodes) 1) + {:node/name :<> + :node/children nodes} + (first nodes)))) + +(defn $ + "Creates a new node + + Usage: + + ```clojure + ($ :div + ($ \"hello world\" )) + ```" + [& opts] + (if (keyword? (first opts)) + (node-from-opts opts) + (nodes-from-opts opts))) diff --git a/test/dompa/coordinates_test.cljc b/test/dompa/coordinates_test.cljc index 9b05826..9d145b0 100644 --- a/test/dompa/coordinates_test.cljc +++ b/test/dompa/coordinates_test.cljc @@ -1,8 +1,12 @@ (ns dompa.coordinates-test - #?(:clj (:require [clojure.test :refer [deftest testing is]] - [dompa.coordinates :as coordinates])) - #?(:cljs (:require [cljs.test :refer-macros [deftest testing is]] - [dompa.coordinates :as coordinates]))) + #?(:clj + (:require + [clojure.test :refer [deftest testing is]] + [dompa.coordinates :as coordinates])) + #?(:cljs + (:require + [cljs.test :refer-macros [deftest testing is]] + [dompa.coordinates :as coordinates]))) (deftest compose-test (testing "Create coordinates" @@ -107,7 +111,7 @@ coordinates/compose coordinates/unify coordinates/->nodes)))) - + (testing "Create nodes from self-closing tags" (is (= [{:node/name :hr :node/attrs {}}] @@ -115,16 +119,16 @@ coordinates/compose coordinates/unify coordinates/->nodes)))) - + (testing "Parse attributes with forward slashes in them" - (is (= [{:node/name :meta, - :node/attrs {:name "route-pattern", - :content "/:user_id/:repository", + (is (= [{:node/name :meta, + :node/attrs {:name "route-pattern", + :content "/:user_id/:repository", :data-turbo-transient true}}] - (-> "" - coordinates/compose - coordinates/unify - coordinates/->nodes)))) + (-> "" + coordinates/compose + coordinates/unify + coordinates/->nodes)))) (testing "Create nodes with attributes" (is (= [{:node/name :div diff --git a/test/dompa/html_test.cljc b/test/dompa/html_test.cljc index 77107d1..5b620ba 100644 --- a/test/dompa/html_test.cljc +++ b/test/dompa/html_test.cljc @@ -1,8 +1,12 @@ (ns dompa.html-test - #?(:clj (:require [clojure.test :refer [deftest is testing]] - [dompa.html :as html])) - #?(:cljs (:require [cljs.test :refer-macros [deftest testing is]] - [dompa.html :as html]))) + #?(:clj + (:require + [clojure.test :refer [deftest is testing]] + [dompa.html :as html])) + #?(:cljs + (:require + [cljs.test :refer-macros [deftest testing is]] + [dompa.html :as html]))) (deftest coordinates-test (testing "HTML to coordinates" diff --git a/test/dompa/nodes_test.cljc b/test/dompa/nodes_test.cljc index ca8d483..53e502a 100644 --- a/test/dompa/nodes_test.cljc +++ b/test/dompa/nodes_test.cljc @@ -1,13 +1,16 @@ (ns dompa.nodes-test - #?(:clj (:require - [clojure.test :refer [deftest is testing]] - [clojure.zip :as zip] - [dompa.html :as html] - [dompa.nodes :as nodes :refer [$ defhtml]])) - #?(:cljs (:require [cljs.test :refer-macros [deftest testing is]] - [clojure.zip :as zip] - [dompa.nodes :as nodes :refer [$] :refer-macros [defhtml]] - [dompa.html :as html]))) + #?(:clj + (:require + [clojure.test :refer [deftest is testing]] + [clojure.zip :as zip] + [dompa.html :as html] + [dompa.nodes :as nodes :refer [$ defhtml]])) + #?(:cljs + (:require + [cljs.test :refer-macros [deftest testing is]] + [clojure.zip :as zip] + [dompa.nodes :as nodes :refer [$] :refer-macros [defhtml]] + [dompa.html :as html]))) (defhtml hello [who] ($ :div @@ -21,7 +24,7 @@ ($ :ul (->> items (map (fn [item] - ($ :li ($ item)))) + ($ :li item))) (into [])))) (deftest list-items-test @@ -33,7 +36,7 @@ (is (= {:node/name :div :node/children [{:node/name :dompa/text :node/value "hello world"}]} - ($ :div ($ "hello world"))))) + ($ :div "hello world")))) (testing "a fragment node" (is (= {:node/name :<> @@ -47,7 +50,71 @@ ($ :span ($ "hello")) ($ :span - ($ "world"))))))) + ($ "world")))))) + + (testing "a nil node" + (is (= nil + ($ nil)))) + + (testing "a string node" + (is (= {:node/name :dompa/text + :node/value "hello world"} + ($ "hello world")))) + + (testing "a node of multiple sub-nodes" + (is (= {:node/name :<> + :node/children [{:node/name :dompa/text + :node/value "hello"} + {:node/name :dompa/text + :node/value "12345"} + {:node/name :dompa/text + :node/value "123.3"} + {:node/name :dompa/text + :node/value "world"}]} + ($ "hello" 12345 nil 123.3 "world"))) + + (is (= {:node/name :<> + :node/children [{:node/name :dompa/text + :node/value "hello"} + {:node/name :dompa/text + :node/value "12345"} + {:node/name :<> + :node/children [{:node/name :dompa/text + :node/value "w"} + {:node/name :dompa/text + :node/value "o"} + {:node/name :dompa/text + :node/value "r"} + {:node/name :dompa/text + :node/value "l"} + {:node/name :dompa/text + :node/value "d"}]}]} + ($ "hello" 12345 (map #($ %) ["w" "o" "r" "l" "d"])))) + + (is (= {:node/name :<> + :node/children [{:node/name :dompa/text + :node/value "hello"} + {:node/name :<> + :node/children [{:node/name :dompa/text + :node/value "w"} + {:node/name :<> + :node/children [{:node/name :dompa/text + :node/value "o"} + {:node/name :dompa/text + :node/value "r"} + {:node/name :<> + :node/children [{:node/name :dompa/text + :node/value "l"} + {:node/name :dompa/text + :node/value "d"}]}]}]}]} + ($ "hello" + (map (fn [x] + ($ x)) + ["w" (map (fn [x] + ($ x)) + ["o" "r" (map (fn [x] + ($ x)) + ["l" "d"])])])))))) (deftest traverse-test (let [traverser-fn (fn [node] From 4a242170a0dbc3df30ba56608a581ab1a9fcf61c Mon Sep 17 00:00:00 2001 From: Asko Nomm Date: Sun, 7 Dec 2025 01:18:18 +0200 Subject: [PATCH 2/2] Simplify conditional --- src/dompa/nodes.cljc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/dompa/nodes.cljc b/src/dompa/nodes.cljc index 3e5c72f..d2f8e4b 100644 --- a/src/dompa/nodes.cljc +++ b/src/dompa/nodes.cljc @@ -132,18 +132,26 @@ attrs? (assoc :node/attrs attrs) (seq children) (assoc :node/children (flatten children)))))) +(defn- list-of-one? + [coll] + (and (sequential? coll) + (= (count coll) 1))) + +(defn- list-of-many? + [coll] + (and (sequential? coll) + (> (count coll) 1))) + (defn- nodes-from-opt [opt] (cond (map? opt) opt - (and (sequential? opt) - (> (count opt) 1)) + (list-of-many? opt) {:node/name :<> :node/children opt} - (and (sequential? opt) - (= (count opt) 1)) + (list-of-one? opt) (first opt) :else