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
.
- Install
- boot
- Pandoc and pandoc-sidenote:
sudo apt-get install pandoc
,sudo apt-get install pandoc-sidenote
- To build the website, run
boot build
. - To start a development server, run
boot dev
.
- To run boot-livereload a JavaScript file named
livereload.js
must be saved underresources/js/livereload.js
. More information is available from the LiveReload website, but the script is pretty simple:document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')
. - Install CSS
- Tufte CSS v1.8.0:
/resources/public/css/tufte.min.css
- ET Book:
/resources/public/css/et-book/*
- Tachyons v4.12.0:
/resources/public/css/tachyons.min.css
- Tufte CSS v1.8.0:
- Included CSS
- Font Awesome v 5.14.0
- 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 2020-04-11-perun-blog-2.md -f markdown -t html5 -o test.html
- The default is equivalent to:
- Pandoc Sidenote for creating sidenotes rather than footnotes.
- Called by
pandoc --filter pandoc-sidenote
. - In Perun, default
["-f" "markdown" "-t" "html5"]
→["--from" "markdown" "--to" "html5" "--filter" "pandoc-sidenote"]
sudo apt-set install pandoc-sidenote
- Called by
:cmd-opts
— CMD line options to pass to pandoc, (default["-f" "markdown" "-t" "html5"]
, which converts markdown files to html5).
Erik came up with a great way to embed videos in Tufte and keep them 16x9.
<figure>
<div class="iframe-wrapper">
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allowfullscreen></iframe>
</div>
</figure>
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 latest tufte.css, “1.8.0”, adds support for dark mode. I don’t want to deal with that so I’m sticking with “1.7.2”.
https://github.com/edwardtufte/tufte-css/commit/957e9c6dc3646ab1847ebe41fdb853e59ecf8579
/* Adds dark mode */
@media (prefers-color-scheme: dark) {
body {
background-color: #151515;
color: #ddd;
}
}
/* Adds dark mode */
@media (prefers-color-scheme: dark) {
a:link, .tufte-underline, .hover-tufte-underline:hover {
text-shadow: 0.03em 0 #151515, -0.03em 0 #151515, 0 0.03em #151515, 0 -0.03em #151515, 0.06em 0 #151515, -0.06em 0 #151515, 0.09em 0 #151515, -0.09em 0 #151515, 0.12em 0 #151515, -0.12em 0 #151515, 0.15em 0 #151515, -0.15em 0 #151515;
}
}
;; DO NOT EDIT
;; This file is tangled from README.org.
(set-env!
: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"]
[ring/ring-mock "0.4.0"]
[cljc.java-time "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 undercontent
whileresources
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.
TODO:
- 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]])
Is it a blog post, a webpage, or a book review? Has it been published?
post?
andbook?
is based on the path.program?
pulls fromposts
,books
, andprograms
but only selects things tagged#clojure
.
(defn page? [{:keys [original-path] :as meta}]
(if original-path
(.startsWith original-path "pages/")
false))
(defn post? [{:keys [original-path] :as meta}]
(if original-path
(.startsWith original-path "posts/")
false))
(defn archive? [{:keys [original-path] :as meta}]
(when original-path
(clojure.string/includes? original-path "archive")))
(defn book?
"In: {:original-path \"books\"}"
[{:keys [original-path] :as meta}]
(if original-path
(.startsWith original-path "books/")
false))
(defn program?
"In: {:original-path \"programs\"}"
[{:keys [original-path] :as meta}]
(if original-path
(.startsWith original-path "programs/")
false))
(defn tagged-clojure?
"Note: an essay tagged #clojure could exist in /books, /posts, /program"
[{:keys [tags original-path] :as meta}]
(when (some #(= "clojure" %) tags)
true))
(defn published?
"In: {:date-published \"yes\"}"
[{:keys [date-published] :as meta}]
(if date-published true false))
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 [tagged-clojure? 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 'site.cv/render
: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? #((comp not archive?) %)]))
(perun/rss :site-title "Beyond the Frame: Clojure" :description "Essays about the Clojure programming language"
:filterer (apply every-pred [tagged-clojure? published?]) :filename "btf-clojure-feed.rss")
(target)))
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 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)
(build)
(perun/inject-scripts :scripts #{"js/livereload.js"})
(livereload :asset-path "public" :filter #"\.(css|html|js)$")
(serve :resource-root "public")))
There is no nrepl present. The current process:
boot dev
orboot dev repl
if you want a REPL.- Open a project file,
*.clj
, andcider-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. The current Cider Boot Parameters look like this: repl -s -b localhost wait
. Just prefix the dev
command and run: dev repl -s -b localhost wait
. But refresh-on-save does not seem to work.
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: https://perun.io/guides/getting-started/
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 schmud.de:21 -starttls ftp
- I include the certificate chain in a new file called
mycert.crt
in the local ~/.lftp folder. - I create a file called
rc
in the local ~/.lftp folder and add the linesset ssl:ca-file "mycert.crt"
set ssl:check-hostname no
(this preventsFatal error: Certificate verification: certificate common name doesn't match requested host name ‘<ftp-hostname>’
when running a command likels
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 fileset 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 locallylcd
: local change directory
(comment
(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)
)
<s
→TAB
: write a code block in a .org file.C-c-v-t
: tangle the file and produceC-c
: evaluate the Clojure codeC-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 thandrawer
, otherwise it will not render correctly in GitHub.- Clojure + Literate Programming originally inspired by Literate Clojure Ants