Last active
March 15, 2021 14:47
-
-
Save adam-james-v/25e7a241a23cf33160aa0e0759ef985e to your computer and use it in GitHub Desktop.
Minimal implementation of a Hiccup Compiler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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