Skip to content


Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Beyond the Frame

This is the code for my blog and personal website, Beyond the Frame.

This README is also the build.boot file as a literate Babel document (aka org-babel). CTRL-c-v-t in Emacs will tangle code and generate the build.boot.

  • To build the website, run boot build.
  • To start a development server, run boot dev.

Non-Clojure Dependencies

  • To run boot-livereload a JavaScript file named livereload.js must be saved under resources/js/livereload.js. More information is available from the LiveReload website, but the script is pretty simple: document.write('<script src="http://' + ( || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>').
  • CSS


  • Tufte Pandoc CSS
  • perun/pandoc builds the HTML from markdown in Perun.
    • sudo apt install pandoc
    • Supports footnotes, unlinke the default Perun plugin.
      • The default is equivalent to: pandoc -f markdown -t html5 -o test.html
    • Pandoc Sidenote for creating sidenotes rather than footnotes.
      • Called by pandoc --filter pandoc-sidenote.
      • In Perun, default ["-f" "markdown" "-t" "html5"] &rarr; ["--from" "markdown" "--to" "html5" "--filter" "pandoc-sidenote"]
      • sudo apt-set install pandoc-sidenote
    • :cmd-opts — CMD line options to pass to pandoc, (default ["-f" "markdown" "-t" "html5"], which converts markdown files to html5).

Tufte CSS

Erik came up with a great way to embed videos in Tufte and keep them 16x9.

    <div class="iframe-wrapper">
        <iframe src="" frameborder="0" allowfullscreen></iframe>

But on mobile it doesn’t reach its full width.

figure {
    max-width: 90%;

So it can be centered.

figure {
    max-width: 90%;
    display: block;
    margin-left: auto;
    margin-right: auto;

The Boot Build File

Create the Environment

;; This file is tangled from

 :source-paths #{"src" "content"}
 :resource-paths #{"resources"}
 :dependencies '[[perun "0.4.3-SNAPSHOT" :scope "test"]
                 [nrepl "0.7.0" :scope "test"]
                 [hiccup "1.0.5" :exclusions [org.clojure/clojure]]
                 [javax.xml.bind/jaxb-api "2.3.0"] ;; new requirement after local system update
                 [pandeiro/boot-http "0.8.3" :exclusions [org.clojure/clojure]]
                 [deraen/boot-livereload "0.2.1"]
                 [time-literals "0.1.4"]
                 [ "0.1.11"]])

A few quick notes on the configuration.

  • :resource-paths #{"resources" "content"}: I keep my raw files in two places. My original writing is under content while resources has all of the necessary images, css, js, etc….
  • [deraen/boot-livereload "0.2.1"] : boot-livereload is an important part of any boot flow involving a browser. It reloads the browser when any files are changed.


  • The [javax.xml.bind/jaxb-api "2.3.0"] requirement?
  • The two time requirements?
(require '[io.perun :as perun]
         '[pandeiro.boot-http :refer [serve]]
         '[deraen.boot-livereload :refer [livereload]])

Helper Functions

Is it a blog post, a webpage, or a book review? Has it been published?

  • post? and book? is based on the path.
  • program? pulls from posts, books, and programs but only selects things tagged #clojure.
(defn page? [{:keys [original-path] :as meta}]
  (if original-path
    (.startsWith original-path "pages/")

(defn post? [{:keys [original-path] :as meta}]
  (if original-path
    (.startsWith original-path "posts/")

(defn book?
  "In: {:original-path \"books\"}"
  [{:keys [original-path] :as meta}]
  (if original-path
    (.startsWith original-path "books/")

(defn program?
  [{:keys [tags original-path] :as meta}]
  (when (some #(= "clojure" %) tags)

(defn published?
  "In: {:date-published \"yes\"}"
  [{:keys [date-published] :as meta}]
  (if date-published true false))

The Main Build Task

This is the build task that builds the static site.

TODO: why do all css/ dirs update every time I render, but not the *.css files

(deftask build []
  (comp (perun/global-metadata :filename "site.base.edn")
        (perun/pandoc :cmd-opts ["--from" "markdown" "--to" "html5" "--filter" "pandoc-sidenote"])
        (perun/collection :renderer 'site.core/render-index-page :page "index.html"
                          :filterer (apply every-pred [post? published?]))
        (perun/collection :renderer 'site.core/render-index-page :page "books.html"
                          :filterer (apply every-pred [book? published?]))
        (perun/collection :renderer 'site.core/render-programs-index-page :page "programs.html"
                          :filterer (apply every-pred [program? published?]))
        (perun/render :renderer 'site.core/render-post-pages
                      :filterer (apply every-pred [post? published?])
                      :meta {:type "post"})
        (perun/render :renderer 'site.core/render-book-pages
                      :filterer (apply every-pred [book? published?])
                      :meta {:type "book"})
        (perun/render :renderer 'site.core/render-post-pages
                      :filterer (apply every-pred [program? published?])
                      :meta {:type "program"})
        (perun/tags :renderer 'site.core/render-tag-pages
                    :filterer (apply every-pred [(some-fn book? post? program?) published?])
                    :out-dir "public/tags")
        (perun/render :renderer 'site.core/render-post-pages
                      :filterer page?
                      :meta {:type "page"})
        (perun/static :renderer '
                      :page "cv.html"
                      :meta {:type "page"})
        (perun/static :renderer 'site.timeline/render
                      :page "timeline.html"
                      :meta {:type "page"})
        (perun/collection :renderer 'site.previous-entries/render
                          :page "previous-entries.html"
                          :filterer (apply every-pred [(some-fn book? post? program?) published?]))
        (perun/rss :filterer (apply every-pred [post? published?]))
        (perun/rss :site-title "Beyond the Frame: Clojure" :description "Essays about the Clojure programming language"
                   :filterer (apply every-pred [program? published?]) :filename "btf-clojure-feed.rss")

It’s complex task, so I broke out a few details below.

This site must render through pandoc to render tufte css-style sidenotes from the Markdown source. The pandoc-sidenote plugin does the heavy lifting.

The collection task renders links to all previous posts in index.html. render actually does the rendering.

(perun/pandoc :cmd-opts ["--from" "markdown" "--to" "html5" "--filter" "pandoc-sidenote"])
(perun/collection :renderer 'site.core/render-index-page :page "index.html"
                  :filterer (apply every-pred [post? published?]))
(perun/render :renderer 'site.core/render-post-pages
              :filterer (apply every-pred [post? published?])
              :meta {:type "post"})

The Development Task

The dev task sandwiches the build function between watch and serve. The former watches for any changes to your files and automatically recompiles. The latter serves those files to a web browser.

Perun offers an elegant way to inject the script into every page in the development environment. (livereload :asset-path "public" :filter #"\.(css|html|js)$") lets livereload know what to look for, while (perun/inject-scripts :scripts #{"js/livereload.js"}) loads the actual script.

(deftask dev []
  (comp (watch)
        (perun/inject-scripts :scripts #{"js/livereload.js"})
        (livereload :asset-path "public" :filter #"\.(css|html|js)$")
        (serve :resource-root "public")))

Development Environment

There is no nrepl present. The current process:

  1. boot dev
  2. Open a project file, *.clj, and cider-jack-in.

Updating the file will update the website.

Customize the jack-in command by tweaking the shell command it runs to include a dev profile.

C-u M-x cider-jack-in to specify the exact command for cider-jack-in. Specifically, add the dev profile.

I need to customize the command line CIDER uses for cider-jack-in by modifying the following string options:

  • cider-boot-global-options: these are passed to the command directly, in first position (e.g., -o to lein enables offline mode).
  • cider-boot-parameters: these are usually task names and their parameters (e.g., dev for launching boot’s dev task instead of the standard repl -s wait).


Plugins I would like to add someday:

  • (perun/sitemap :filename "sitemap.xml")
  • (perun/ttr)
  • (perun/word-count)
  • (perun/build-date)
  • (perun/paginate :renderer 'io.perun.example.paginate/render)
  • (perun/sitemap)
  • (perun/atom-feed :filterer :original)


Insert (perun/print-meta) into the (deftask dev [] ...) command to troubleshoot the build process.

boot --verbose build

boot show -f perun/markdown show -f: To inspect the files and metadata that is passed from task to task, there are two tasks we can use. The Boot built-in task show includes a convenient option to display a tree of all files in the fileset. To see how a task changes the fileset, you can use it like this:

Regular build bug: [inject-scripts] - copied unchanged file public/posts/2020-04-29-info-to-knowledge.html


  • elisp fiction to autocomplete keywords (vs. tags)

Tachyons measures widths using the border-box model. Tufte uses the content-box model. I experimented with simply switching the model in my custom css file, btf.css, but it broke the reflow (as expected).

body {
    -moz-box-sizing: content-box;
    -webkit-box-sizing: content-box;
    box-sizing: content-box;

TODO: The ideal solution is to use a custom Tachyons build.


lftp uses Transport Layer Security (TLS). So it’s essential to first grab the certificate from the FTP server.

openssl s_client -connect -starttls ftp
  1. I include the certificate chain in a new file called mycert.crt in the local ~/.lftp folder.
  2. I create a file called rc in the local ~/.lftp folder and add the lines
    • ~set ssl:ca-file “mycert.crt”~
    • set ssl:check-hostname no (this prevents Fatal error: Certificate verification: certificate common name doesn't match requested host name ‘<ftp-hostname>’ when running a command like ls remotely)

Further reading:

Alternatively, it may be possible to use the Ubuntu certificates in some cases:

  • Grab the latest certificates: sudo update-ca-certificates
  • Update the /etc/lftp.conf by pointing to the certificate file ~set ssl:ca-file “/etc/ssl/certs/ca-certificates.crt”~

Alternatively, alternatively certificate errors can be temporarily suppressed using set ssl:verify-certificate false at the lftp prompt

lftp commands include

  • local ls: run command locally
  • lcd: local change directory

Comment Log


  (published? {:date-published nil})
  (published? {:date-published "avril 14th"})

  (def path-data [{:original-path "posts/fefe"} {:original-path nil} {:original-path "po"} {:original-path "fee/fefef"} {:original-path "posts/zzz"} ])

  (def pub-data [{:date-published "avril 14th"} {:date-published nil} {:date-published "may 14th"}])

  (def pub-path-data [{:original-path "posts/fefe" :date-published "avril 14th"} {:original-path nil :date-published "date"} {:original-path "po" :date-published "may 14th"} {:original-path "fee/fefef" :date-published nil} {:original-path "posts/zzz" :date-published "may 14th"} ])

  (filter post? path-data)
  (filter published? pub-path-data)
  (filterv (and post? published?) pub-path-data)
  ; > ({:original-path "posts/fefe", :date-published "avril 14th"}
  ;    {:original-path nil, :date-published "date"}
  ;    {:original-path "po", :date-published "may 14th"}
  ;    {:original-path "posts/zzz", :date-published "may 14th"})
  (filter (or post? published?) pub-path-data)
  ; > ({:original-path "posts/fefe", :date-published "avril 14th"}
  ;    {:original-path "posts/zzz", :date-published "may 14th"})

  (filter (apply every-pred [post? published?]) pub-path-data)
  ; > ({:original-path "posts/fefe", :date-published "avril 14th"}
  ;    {:original-path "posts/zzz", :date-published "may 14th"})

  (map #(and (post? %) (published? %)) pub-path-data) ; (true false false false true)
  (map #(or (post? %) (published? %)) pub-path-data) ; (true true true false true)


Editing Org Mode

  • <s &rarr; TAB: write a code block in a .org file.
    • C-c-v-t: tangle the file and produce
    • C-c: evaluate the Clojure code
    • C-c-e h: export to HTML, C-c-e b see it immediately in a browser window
    • Run these commands with
      • C-c C-c
      • C-c C-o: results in a separate buffer.
  • #+BEGIN_SRC shell :results code: the #+RESULTS: certificate must be as :results code rather than drawer, otherwise it will not render correctly in GitHub.
  • Clojure + Literate Programming originally inspired by Literate Clojure Ants


Beyond the Frame: blog and personal website of David Schmudde







No releases published


No packages published