Skip to content

Instantly share code, notes, and snippets.

@adam-james-v
Last active March 15, 2021 14:47
Show Gist options
  • Save adam-james-v/25e7a241a23cf33160aa0e0759ef985e to your computer and use it in GitHub Desktop.
Save adam-james-v/25e7a241a23cf33160aa0e0759ef985e to your computer and use it in GitHub Desktop.
Minimal implementation of a Hiccup Compiler
;; source code related to the project shown in this video:
;; https://youtu.be/_XiEc0g2wL8
;; author: adam-james
(ns hc.main)
(defn hiccup?
[item]
(and (vector? item)
(keyword? (first item))))
#_(defn key->str
[k]
(let [[f & rest] (str k)]
(if (= \: f)
(apply str rest)
(apply str (conj rest f)))))
;; Turns out there is a simpler way to get this result.
;; I don't use the function above, but will leave it in here
;; to show that it's still possible to write progams without knowing
;; the entire core library. It's also a reminder that there is always
;; more to learn and discover.
(comment
(name :asdf)
;; => "asdf"
)
#_(defn compile-props
[m]
(let [f (fn [[k v]]
(str (name k) "=\"" (str v) "\""))]
(->> m
(map f)
(interpose " ")
(#(conj % " "))
(apply str))))
;; The above fn works until you wish to apply a style map.
;; So I'll refactor a bit:
(defn compile-style
[m]
(let [f (fn [[k v]]
(str (name k) ":" (str v) ";"))]
(->> m
(map f)
(apply str))))
(defn compile-property
[[k v]]
(let [v (if (map? v) (compile-style v) (str v))]
(str (name k) "=\"" v "\"")))
(defn compile-props
[m]
(->> m
(map compile-property)
(interpose " ")
(#(conj % " "))
(apply str)))
;; These are some simple hiccup elements that my impl should handle.
;; In a production situation, it is much smarter to use clojure.test
;; to create some unit tests.
(def a1 [:p])
(def a2 [:p "something"])
(def a3 [:p {:prop "a"}])
(def a4 [:p {:prop "a"} "something"])
(def a5 [:img {:src "./smile.png"}])
(def b1 [:div [:p "a"]])
(def b2 [:div [:p "a"] [:p "b"]])
(def b3 [:div '([:p "a"] [:p "b"])])
(def single-tag #{:img :hr :input :meta :embed :link})
;; implementation
(declare html)
(defn compile-item
[[k & content]]
(let [props (first content)
propss (when (map? props) (compile-props props))
otag (str "<" (name k) propss (when (single-tag k) "/") ">")
ctag (when (not (single-tag k)) (str "</" (name k) ">"))
content (if (map? props) (rest content) content)]
(cond
;; no content
(or (empty? content) (nil? content))
(str otag ctag)
;; content is just a string wrapped in a list
(and (= 1 (count content)) (string? (first content)))
(str otag (first content) ctag)
;; content is some nested/sequential structure
:else
(str otag (apply html content) ctag))))
(defn html
[& content]
(cond
;; fn called with a single string
(and (= 1 (count content)) (string? (first content)))
(first content)
;; fn called with a single hiccup element
(and (= 1 (count content)) (hiccup? (first content)))
(apply compile-item content)
;; fn called with a list of elements
(and (= 1 (count content)) (seq? (first content)))
(apply str (map compile-item (first content)))
;; fn called directly on mulitple elements
:else
(apply str (map html content))))
;; usage
(def drawing
[:svg {:width 200
:height 200
:viewBox "-1 -1 200 200"
:xmlns "http://www.w3.org/2000/svg"}
[:g {:transform "scale(1)"}
'([:g {} [:rect {:width 150 :height 16 :x 13.320 :y 94.589
:transform "rotate(22 88.320 102.589)"
:fill "#aaa"
:stroke-width "2px"
:stroke "black"}]
[:line {:x1 16.533 :y1 80.056 :x2 155.611 :y2 136.248
:stroke-width "2px"
:stroke "#556"}]
[:line {:x1 17.657 :y1 77.275 :x2 156.735 :y2 133.466
:stroke-width "1.5px"
:stroke "#556"}]
[:rect {:width 12 :height 85 :x 40.597 :y 43.232
:transform "rotate(22 46.597 85.732)"
:fill "#556"
:stroke-width "2px"
:stroke "black"}]
[:rect {:width 12 :height 75 :x 29.470 :y 43.737
:transform "rotate(22 35.470 81.237)"
:fill "#556"
:stroke-width "2px"
:stroke "black"}]
[:rect {:width 12 :height 85 :x 124.043 :y 76.946
:transform "rotate(22 130.043 119.446)"
:fill "#556"
:stroke-width "2px"
:stroke "black"}]
[:rect {:width 12 :height 75 :x 135.169 :y 86.442
:transform "rotate(22 141.169 123.942)"
:fill "#556"
:stroke-width "2px"
:stroke "black"}]])]])
(comment
(spit "out.svg" (html drawing))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment