Simple static blog with Clojure
15 Mar 2012This inaugural post for this Clojure blog describes how it is created. This blog is hosted by GitHub Pages and the static pages are generated by a minimal clone for Jekyll written in Clojure and using Enlive. The code is by no means a replacement for Jekyll, but can serve as a starting point for people wanting to do the same.
The code for the static pages generator is in the clj-static-blog project.
Github Pages can host static pages under username.github.com. Their suggested method to generate static pages is with Jekyll. As I could not get Jekyll to run, I had enough of an excuse to use Clojure and Enlive for the task.
What clj-static-blog does:
- Put each post in a template with a header and footer
- Create an index.html with a list of posts
- Create an atom rss feed whit a list of posts
It also includes a small webserver to browse the generated pages locally. GitHub Pages runs Jekyll when changes are pushed to a pages repository. For this blog the static pages are checked-in, rather than the sources, because the generator is not run on GitHub. The Jekyll processing is disabled by the .nojekyll file in this repo.
The core of clj-static-blog is the Enlive library. Enlive is a selector-based (à la CSS) templating library for Clojure, by Christophe Grand. The readme of Enlive points to two good tutorials. Here I just want to highlight to core functionality of Enlive.
Enlive does selection and transformations. Using only the selectors Enlive can be used to scrape websites. With the addition of transformations Enlive can be used for HTML templating.
;; html is seq of {:tag :attrs :content} (def html (html-resource (io/file "/source/_postlist.html"))) ;; => ({:tag :html, :attrs nil, :content ({:tag :body, :attrs ... ;; back to textual html again (emit* html) ;; => ("<" "html" ">" "<" "body" ">" "<" "div" " " "id" "=\"" ... ;; back to a string (apply str (emit* html)) ;; => "<html><body><div id=\"home..... ;; select a seq of nodes from html with the css-like selector (def date-node (select html [:div.date])) ;; => ({:tag :div, :attrs {:class "date"}, :content ("YYYY/MM/DD")}) ;; tranform applies a transformation to a node (transform date-node [:div.date] (fn [match] (assoc match :content "2012/03/15"))) ;; => ({:tag :div, :attrs {:class "date"}, :content "2012/03/15"}) ;; the results of a transformation may also be a seq of nodes (transform date-node [:div.date] (fn [match-node] [{:tag "hr" :attrs nil :content []} {:tag "p" :attrs nil :content "new structure"}])) ;; => ({:tag "hr", :attrs nil, :content []} {:tag "p", :content "new structure", :attrs nil})
This code is also in clj-static-blog/src/clj_static_blog/core.clj.