Setting up a Math/Programming Blog with Hakyll

Posted on June 22, 2014

I’ve recently become interested in starting a blog for various reasons. Since I have jumped head-first into the Haskell programming world, I decided to use a static site generator written in Haskell. The best one out there, of course, is Hakyll.

At the core of Hakyll is the wonderful Pandoc library by John MacFarlane. Pandoc can convert documents between different formats, including Markdown, reStructuredText, and HTML, among many others (check out the graph on the front page of Pandoc’s site). With the power of Pandoc, Hakyll allows you to write the content of your website in a readable markup language, like Markdown or reStructuredText, and convert it to HTML.

Getting Started

Installation and the basics are covered on the Hakyll Website, so I would recommend heading there for the most up-to-date info. Here, I just want to highlight how easy it is to create a starter site:

# Create the skeleton website
$ hakyll-init my-site
$ cd my-site

# Compile the main site binary and compile the content into HTML
$ ghc --make -threaded site.hs
$ ./site build

# Start a server on localhost:8000 to preview your website
$ ./site watch

Static site generators have a reputation for being written by nerds, for nerds. Generally, they can be a pain to set up. Being someone who starts blogs all too often and messed with the likes of Jekyll and Pelican, Hakyll was the easiest to set up.

Next, I’m going to cover some modifications to the default setup, using ideas and code from various blog posts I’ve found.

Code Highlighting

Since I want to include code in my blog, I am going to need suitable syntax highlighting. Pandoc has code blocks that convert code into syntax-highlighted HTML. For example, this:

```python
def hello(x):
    print("Hello,", x)
```

gets turned into this:

def hello(x):
    print("Hello,", x)

We will need some custom CSS to change the background color of source blocks, and also use some pre-generated CSS from Pandoc to do syntax highlighting:

pre.sourceCode {
    background-color: #f8f8f8;
    border: 2px solid #ddd;
    padding: 6px 10px;
    border-radius: 5px;
    overflow-x: auto;
    font-size: 14px;
}


/* Generated by pandoc. */
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
   {
       margin: 0;
       padding: 0;
       border: 0;
       vertical-align: baseline;
       border: none;
   }
td.lineNumbers {
    border-right: 1px solid #AAAAAA;
    text-align: right; color: #AAAAAA;
    padding-right: 5px;
    padding-left: 5px;
}
td.sourceCode { padding-left: 5px; }
.sourceCode span.kw { color: #007020; font-weight: bold; }
.sourceCode span.dt { color: #902000; }
.sourceCode span.dv { color: #40a070; }
.sourceCode span.bn { color: #40a070; }
.sourceCode span.fl { color: #40a070; }
.sourceCode span.ch { color: #4070a0; }
.sourceCode span.st { color: #4070a0; }
.sourceCode span.co { color: #60a0b0; font-style: italic; }
.sourceCode span.ot { color: #007020; }
.sourceCode span.al { color: red; font-weight: bold; }
.sourceCode span.fu { color: #06287e; }
.sourceCode span.re { }
.sourceCode span.er { color: red; font-weight: bold; }

Math

I got the information for including math in Hakyll from this blog post. Pandoc can convert math to Latex and render it using Mathjax. We just have to modify the compilation rules for our posts to detect and render math, and slightly modify our HTML templates to point to Mathjax.

First, we import a couple of modules:

import qualified Data.Set as S
import           Text.Pandoc.Options

Now, we create our pandocMathCompiler, which will be used to compile our blog posts. We pass options to Pandoc in this function to allow us to compile math.

pandocMathCompiler =
    let mathExtensions = [Ext_tex_math_dollars, Ext_tex_math_double_backslash,
                          Ext_latex_macros]
        defaultExtensions = writerExtensions defaultHakyllWriterOptions
        newExtensions = foldr S.insert defaultExtensions mathExtensions
        writerOptions = defaultHakyllWriterOptions {
                          writerExtensions = newExtensions,
                          writerHTMLMathMethod = MathJax ""
                        }
    in pandocCompilerWith defaultHakyllReaderOptions writerOptions

As you can see in mathExtensions, this compiler handles math enclosed in dollar signs, latex macros, or escaped by double backslashes. We then replace all instances of pandocCompiler with pandocMathCompiler in site.hs. Finally, add the following HTML to your default.html template to point users to Mathjax:

<script type="text/javascript"
        src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>

Now, we can type math into our markdown files:

$$ \ln x = \int_{-\infty}^x \frac 1 y \, dy  $$

and get

\[ \ln x = \int_{-\infty}^x \frac 1 y \, dy \].

Drafts

I like having support for drafts, so I can work on a post while having it under version control, but not published. I want to be able to preview drafts on my machine when I use ./site watch, but not deploy them to my web server. I got tips on how to do this from here.

The basic idea is this: create a drafts/ folder and put drafts in there, with the same YYYY-MM-DD-... file name format. We then check if we are using the watch argument by using the getArgs function. If we are, then combine the "posts/*" pattern with "drafts/*" using the .||. combinator:

(action:_) <- getArgs
let postsPattern = if action == "watch"
                   then "posts/*" .||. "drafts/*"
                   else "posts/*"

Finally, replace all instances of "posts/*" with postsPattern. Now when using ./site watch, our index and archive will show the draft posts alongside our completed posts. Using any other argument, like publish, will leave the drafts out.

Conclusion

I will periodically update this blog post as I reconfigure my Hakyll setup. Be sure to check out the Github repo for this website. Thanks for reading!

comments powered by Disqus