Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Clojure DSL to generate static HTML
;; Generating static HTML using Clojure macros
;; It is possible to write a DSL in Clojure that generates HTML markup without the need to write a separate parser and compiler (e.g. HAML).
;; Our aim here would be to write code that converts the following Clojure code into the HTML below it
;; (html
;; (head)
;; (body
;; (h1 "An example")
;; (ul {class "my-list"}
;; (li "one")
;; (li "two")
;; (li "three"))
;; (p {class content title "stop pointing at me!"}
;; Some content with part of it in (strong "bold"))))
;; <html>
;; <head></head>
;; <body>
;; <h1>An example</h1>
;; <ul class="my-list">
;; <li>one</li>
;; <li>two</li>
;; <li>three</li>
;; </ul>
;; <p class="content" title="stop pointing at me!">
;; Some content with part of it in <strong>bold</strong>
;; </p>
;; </body>
;; </html>
;; Macros can be used to accomplish this. First we write a couple of utility functions.
(defn join-str
[sep coll]
(apply str (interpose sep coll)))
(defn content-tag
([tag]
(content-tag tag {} ""))
([tag attrs & contents]
(if-not (map? attrs)
(apply content-tag tag {} (cons attrs contents))
(let [tagname (name tag)
attribute-strings (map (fn [[k v]] (str (name k) "=\"" (name v) "\"")) attrs)
attribute-string (join-str " " attribute-strings)
flatten-content (fn [c] (if (seq? c) (apply content-tag c) c))
body (join-str " " (map flatten-content contents))]
(format "<%s %s>%s</%s>" tagname attribute-string body tagname)))))
;; With the bulk of the work done in the utility functions, we can write a macro to sweeten the syntax
(defmacro markup
[& elements]
`(apply str (map (partial apply content-tag) '~elements)))
;; Using this for our example:
(markup
(html
(head)
(body
(h1 "An example")
(ul {class "my-list"}
(li "one")
(li "two")
(li "three"))
(p {class content title "stop pointing at me!"}
Some content with part of it in (strong "bold")))))
;; Despite the lack of pretty printing, the code works and generates valid HTML.
;; Note: This example is meant to demonstrate some features macros provide that make them useful to write Domain Specific Languages (DSLs) in Clojure.
;; In fact, this is NOT a good use of macros, unless you are only planning on using it to generate static markup
;; Although this is a very cool trick, there are some downsides to using macros in this manner:
;; Variables passed to the markup macro are not resolved, so it is not possible to use pass variables.
;; For example: (let [s "some content"] (markup (p s)))
;; generates the following string: "<p >s</p>"
;; and not: "<p >some content</p>"
;; But more generally, macros are expanded at compile time, and are not composable in the same way that dynamically defined functions are. This essentially limits the use of our code here to generating static HTML.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.