Skip to content
2 changes: 1 addition & 1 deletion deps.edn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}
{:deps {org.clojure/clojure {:mvn/version "1.11.4"}}
:paths ["src"]

:aliases
Expand Down
17 changes: 13 additions & 4 deletions src/dev/onionpancakes/chassis/compiler.clj
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,17 @@
(attrs-compiler-expr? (.-init b))))

(extend-protocol AttributesCompilerExpr
#_#_
clojure.lang.Compiler$NilExpr
(attrs-compiler-expr? [_] true)
#_#_
clojure.lang.Compiler$MapExpr
(attrs-compiler-expr? [_] true)
#_#_
clojure.lang.Compiler$EmptyExpr
(attrs-compiler-expr? [this]
(attrs-type? (.getJavaClass this)))
#_#_
clojure.lang.Compiler$ConstantExpr
(attrs-compiler-expr? [this]
;; ConstantExpr not public class, can't call getJavaClass() method.
Expand Down Expand Up @@ -268,10 +272,15 @@
(if-let [res (and (not *evaluated*)
(bound? #'*env*)
(resolve *env* this))]
(let [val (if (var? res) @res res)]
;; Use constant? as guard against un-embedable code.
;; Works for now...
(if (constant? val) val this))
(if (var? res)
(if (or (:dynamic (meta res))
(:redef (meta res)))
this
(let [val @res]
;; Use constant? as guard against un-embedable code.
;; Works for now...
(if (constant? val) val this)))
(if (constant? res) res this))
this))
java.util.Date
(attrs? [_] false)
Expand Down
156 changes: 71 additions & 85 deletions src/dev/onionpancakes/chassis/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -161,68 +161,68 @@
"Batch append strings to Appendable."
([this] this)
([^Appendable this a]
(doto this
(.append ^String a)))
(.. this
(append ^CharSequence a)))
([^Appendable this a b]
(doto this
(.append ^String a)
(.append ^String b)))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)))
([^Appendable this a b c]
(doto this
(.append ^String a)
(.append ^String b)
(.append ^String c)))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)
(append ^CharSequence c)))
([^Appendable this a b c d]
(doto this
(.append ^String a)
(.append ^String b)
(.append ^String c)
(.append ^String d)))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)
(append ^CharSequence c)
(append ^CharSequence d)))
([^Appendable this a b c d e]
(doto this
(.append ^String a)
(.append ^String b)
(.append ^String c)
(.append ^String d)
(.append ^String e)))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)
(append ^CharSequence c)
(append ^CharSequence d)
(append ^CharSequence e)))
([^Appendable this a b c d e f]
(doto this
(.append ^String a)
(.append ^String b)
(.append ^String c)
(.append ^String d)
(.append ^String e)
(.append ^String f)))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)
(append ^CharSequence c)
(append ^CharSequence d)
(append ^CharSequence e)
(append ^CharSequence f)))
([^Appendable this a b c d e f g]
(doto this
(.append ^String a)
(.append ^String b)
(.append ^String c)
(.append ^String d)
(.append ^String e)
(.append ^String f)
(.append ^String g)))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)
(append ^CharSequence c)
(append ^CharSequence d)
(append ^CharSequence e)
(append ^CharSequence f)
(append ^CharSequence g)))
([^Appendable this a b c d e f g h]
(doto this
(.append ^String a)
(.append ^String b)
(.append ^String c)
(.append ^String d)
(.append ^String e)
(.append ^String f)
(.append ^String g)
(.append ^String h)))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)
(append ^CharSequence c)
(append ^CharSequence d)
(append ^CharSequence e)
(append ^CharSequence f)
(append ^CharSequence g)
(append ^CharSequence h)))
([^Appendable this a b c d e f g h i]
(doto this
(.append ^String a)
(.append ^String b)
(.append ^String c)
(.append ^String d)
(.append ^String e)
(.append ^String f)
(.append ^String g)
(.append ^String h)
(.append ^String i))))
(.. this
(append ^CharSequence a)
(append ^CharSequence b)
(append ^CharSequence c)
(append ^CharSequence d)
(append ^CharSequence e)
(append ^CharSequence f)
(append ^CharSequence g)
(append ^CharSequence h)
(append ^CharSequence i))))

(def append-to
"Fascade to function used for fragment appends. By default, it is set to
Expand Down Expand Up @@ -283,44 +283,35 @@
(when-some [v-frag (attribute-value-fragment v)]
(let [k-frag (escape-attribute-value-fragment (name k))]
(if (pos? (.length sb)) ; Note: if not empty, appends space as prefix!
(doto sb
(.append " ")
(.append k-frag)
(.append ": ")
(.append v-frag)
(.append ";"))
(doto sb
(.append k-frag)
(.append ": ")
(.append v-frag)
(.append ";"))))))
(.. sb
(append " ")
(append k-frag)
(append ": ")
(append v-frag)
(append ";"))
(.. sb
(append k-frag)
(append ": ")
(append v-frag)
(append ";"))))))
sb)

(extend-protocol AttributeValueFragment
clojure.lang.Keyword
(attribute-value-fragment [this]
(if-let [ns-str (namespace this)]
(let [ns-frag (escape-attribute-value-fragment ns-str)
name-frag (escape-attribute-value-fragment (.getName this))
sb (doto (StringBuilder.)
(.append ns-frag)
(.append "/")
(.append name-frag))]
(.toString sb))
(escape-attribute-value-fragment (.getName this))))
(escape-attribute-value-fragment (.toString (.-sym this))))
clojure.lang.IDeref
(attribute-value-fragment [this]
(escape-attribute-value-fragment (.deref this)))
(attribute-value-fragment (.deref this)))
clojure.lang.Fn
(attribute-value-fragment [this]
(escape-attribute-value-fragment (this)))
(attribute-value-fragment (this)))
java.util.Collection
(attribute-value-fragment [this]
(let [sb (StringBuilder.)
xf (comp (keep attribute-value-fragment)
(interpose " "))
rf (completing (memfn ^StringBuilder append s))
_ (transduce xf rf sb this)]
_ (transduce xf append-to-appendable sb this)]
(.toString sb)))
java.util.Map
(attribute-value-fragment [this]
Expand Down Expand Up @@ -853,14 +844,9 @@
(extend-protocol Token
clojure.lang.Keyword
(append-fragment-to [this sb]
(if-some [ns-str (namespace this)]
(let [ns-frag (escape-text-fragment ns-str)
name-frag (escape-text-fragment (.getName this))]
(append-to sb ns-frag "/" name-frag))
(let [name-frag (escape-text-fragment (.getName this))]
(append-to sb name-frag))))
(append-to sb (escape-text-fragment (.toString (.-sym this)))))
(fragment [this]
(escape-text-fragment this))
(escape-text-fragment (.toString (.-sym this))))
java.util.UUID
(append-fragment-to [this sb]
;; Not escaped. Should be safe.
Expand Down
23 changes: 23 additions & 0 deletions test/dev/onionpancakes/chassis/tests/test_compiler.clj
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,28 @@
[:div {:foo example-constant}]
[c/doctype-html5 [:div "foo" c/nbsp "bar"]]))

(def ^:dynamic *example-dynamic*
"foobar")

(defn example-fn-dynamic []
(cc/compile [:div nil *example-dynamic*]))

(deftest test-compile-dynamic
(is (= (c/html (example-fn-dynamic)) "<div>foobar</div>"))
(binding [*example-dynamic* "foobarbaz"]
(is (= (c/html (example-fn-dynamic)) "<div>foobarbaz</div>"))))

(def ^:redef example-redef
"foobar")

(defn example-fn-redef []
(cc/compile [:div nil example-redef]))

(deftest test-compile-redef
(is (= (c/html (example-fn-redef)) "<div>foobar</div>"))
(with-redefs [example-redef "foobarbaz"]
(is (= (c/html (example-fn-redef)) "<div>foobarbaz</div>"))))

;; Attributes reflection tests
;; Warnings are emitted at compile time,
;; so warning detection is a side effect?
Expand Down Expand Up @@ -302,6 +324,7 @@
"foobar"])

;; LocalBinded attrs literals
#_#_#_#_
(let [attrs nil]
(cc/compile [:div attrs "foobar"]))
(let [attrs {}]
Expand Down
38 changes: 26 additions & 12 deletions test/dev/onionpancakes/chassis/tests/test_core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -324,16 +324,25 @@
[:div {:foo true}] "<div foo></div>"
[:div {:foo false}] "<div></div>"
[:div {:foo (delay "bar")}] "<div foo=\"bar\"></div>"
[:div {:foo (delay :bar)}] "<div foo=\"bar\"></div>"
[:div {:foo (delay (delay "bar"))}] "<div foo=\"bar\"></div>"
[:div {:foo (fn [] "bar")}] "<div foo=\"bar\"></div>"
[:div {:foo (fn [] :bar)}] "<div foo=\"bar\"></div>"
[:div {:foo (fn [] (fn [] "bar"))}] "<div foo=\"bar\"></div>"

;; Escapes
[:div#<>&'] "<div id=\"&lt;&gt;&amp;&apos;\"></div>"
[:div.<>&'] "<div class=\"&lt;&gt;&amp;&apos;\"></div>"
[:div#<>&'.<>&'] "<div id=\"&lt;&gt;&amp;&apos;\" class=\"&lt;&gt;&amp;&apos;\"></div>"
[(keyword "div#<>&\"'")] "<div id=\"&lt;&gt;&amp;&quot;&apos;\"></div>"
[(keyword "div.<>&\"'")] "<div class=\"&lt;&gt;&amp;&quot;&apos;\"></div>"
[(keyword "div#<>&\"'.<>&\"'")] "<div id=\"&lt;&gt;&amp;&quot;&apos;\" class=\"&lt;&gt;&amp;&quot;&apos;\"></div>"
[:div {:foo "< > & \" '"}] "<div foo=\"&lt; &gt; &amp; &quot; &apos;\"></div>"
[:div#<>&'] "<div id=\"&lt;&gt;&amp;&apos;\"></div>"
[:div.<>&'] "<div class=\"&lt;&gt;&amp;&apos;\"></div>"
[:div#<>&'.<>&'] "<div id=\"&lt;&gt;&amp;&apos;\" class=\"&lt;&gt;&amp;&apos;\"></div>"
[(keyword "div#<>&\"'")] "<div id=\"&lt;&gt;&amp;&quot;&apos;\"></div>"
[(keyword "div.<>&\"'")] "<div class=\"&lt;&gt;&amp;&quot;&apos;\"></div>"
[(keyword "div#<>&\"'.<>&\"'")] "<div id=\"&lt;&gt;&amp;&quot;&apos;\" class=\"&lt;&gt;&amp;&quot;&apos;\"></div>"
[:div {:foo "< > & \" '"}] "<div foo=\"&lt; &gt; &amp; &quot; &apos;\"></div>"
[:div {:foo :<>&'}] "<div foo=\"&lt;&gt;&amp;&apos;\"></div>"
[:div {:foo :<>&'/<>&'}] "<div foo=\"&lt;&gt;&amp;&apos;/&lt;&gt;&amp;&apos;\"></div>"
[:div {:foo (reify Object
(toString [_]
"< > & \" '"))}] "<div foo=\"&lt; &gt; &amp; &quot; &apos;\"></div>"

;; Escapes in class merge
[(keyword "div.<>&\"'") {:class "<>&\"'"}] "<div class=\"&lt;&gt;&amp;&quot;&apos; &lt;&gt;&amp;&quot;&apos;\"></div>"
Expand Down Expand Up @@ -367,7 +376,7 @@
[:div#foo.bar {0 "foo"}] "<div id=\"foo\" class=\"bar\"></div>"))

(deftest test-html-tokens
(are [node s] (= (c/html node) s)
(are [node s] (= (c/html node) (c/fragment node) s)
nil ""
"" ""
"foo" "foo"
Expand All @@ -377,7 +386,14 @@
0.0 "0.0"
(java.util.UUID. 0 0) "00000000-0000-0000-0000-000000000000"
(reify Object
(toString [_] "foo")) "foo"))
(toString [_] "foo")) "foo"
;; Escapes
"< > & \" '" "&lt; &gt; &amp; \" '"
:<>& "&lt;&gt;&amp;"
:<>&/<>& "&lt;&gt;&amp;/&lt;&gt;&amp;"
(reify Object
(toString [_]
"< > & \" '")) "&lt; &gt; &amp; \" '"))

(deftest test-html-nodes
(are [node s] (= (c/html node) s)
Expand All @@ -390,9 +406,7 @@
(fn [] :div) "div"
(reify Object
(toString [_] "div")) "div"
nil ""
;; Escapes
"< > & \" '" "&lt; &gt; &amp; \" '"))
nil ""))

(deftest test-html-deref-node-derefed-once
(let [counter (atom 0)
Expand Down
Loading