3.26 lightyears away

As I promised earlier I’d like to give you some insight into this blogs inner workings, in the hopes that it proofs useful to somebody else. To start of we get into the topic of my additionally keywords.

The task at hand

In my previous Jekyll setup I had added keywords to embed youtube and vimeo videos by writing the following (I only give you the youtube version, I trust in your imagination that get how it’s done for vimeo).

{% youtube $vid %}

This would insert the following markup:

<div class="elastic-container">
  <iframe src="//www.youtube.com/embed/$vid$" frameborder="0" allowfullscreen/>
</div>

With some additional CSS voodoo for the elastic-container, you can read up on here, this makes a responsive video.

Since I would probably forget the elstic-container 9 out of 10 times and resizing my browser before deploying the site will probably not find it’s way into my usual workflow, I had to rebuild this feature for Hakyll.

Ad hoc parsing

Doing this in Hakyll is quite easily done with regular expression magic. And since I programmed in perl for a long time, I could probably do that very quickly. Especially since I could just have lifted it from this description, of a similar use case.

The bad thing about this ad hoc parsing is, that I would have to either hard code the substitution or go with some file reading process. And since the later would amount to about the same amount of work a proper solution would, I opted for a that.

Parsec to the rescue

What is a proper solution you might ask. The answer is easy I added a compiler stage before the pandoc compiler. I first rely on Hakyll to parse the Markdown files into a Compiler (Item String) and then parse the String for my Keywords which start with a §. With a little bit of Parsec magic this looks like this in Haskell:

simpleIdParserGenerator :: String -> (String -> KeywordElement) -> Parser KeywordElement
simpleIdParserGenerator identifier constructor = try $ do
        void $ string ("§"++ identifier ++ "(")
        id <- many1 $ noneOf ")"
        void $ string ")§"
        return $ constructor id

slideshare :: Parser KeywordElement
slideshare = simpleIdParserGenerator "slideshare" SlideShare

youtube :: Parser KeywordElement
youtube = simpleIdParserGenerator "youtube" Youtube

vimeo :: Parser KeywordElement
vimeo = simpleIdParserGenerator "vimeo" Vimeo

As you can see I generalised a parser where you can take any Keyword that is just followed by an id and have a parser generated for you. This parser just takes a “§” followed by an identifier like “youtube” and “(” any string as an id followed by an “)§”. And returns whatever is generated by the supplied constructor. Should any service come up with the idea of using § in their id’s I’d have to change this so some form of escaping works inside of the ids, but that is currently not necessary (I think).

There are some additional parsers that deal with Chunks of text that do not contain §’s at all, Escaped §’s and Tikz pictures, which I will get to in a later post.

Writing stage

After I now have a parsed String I simply need to write the matching substitution. And Since I didn’t want to hard code the output in the Haskell files, I had to come with a different solution. Fortunately Hakyll already provides a Template facility which is perfect for this purpose. Writing out a Youtube video than simply looks like this:


externalResource :: Identifier -> String -> String -> Compiler (Item String)
externalResource templateId fieldName id = do 
                  makeItem "" 
                  >>= loadAndApplyTemplate templateId (constField fieldName id)


youtube :: String -> Compiler (Item String)
youtube = externalResource "templates/youtube.html" "vid"

With the template being the example from above.

With similar code for Chunk, Youtube and Vimeo video and so on this results in a Compiler String for every parsed keyword. So in the end all of it has to be regrouped into a single Compiler (Item String) which this code snippet does.


applyKeywords :: Compiler (Item String)
applyKeywords = do
  b <- getResourceBody 
  aplKeywords b
  
aplKeywords :: Item String -> Compiler (Item String)
aplKeywords item = do
  body <- applyKeywords' $ readKeywords $ itemBody item
  return $ itemSetBody body item

applyKeywords' :: Keywords -> Compiler String
applyKeywords' kws = do
  items <- sequence $ map applyKWs $ unKeyword kws
  return $ concat $ map itemBody items
  where
    applyKWs (Chunk c) = makeItem c
    applyKWs (Escaped) = makeItem "§"
    applyKWs m@(Youtube vid) = youtube vid
    applyKWs m@(Vimeo vid) = vimeo vid
    applyKWs t@(Tikz _ _) = makeItem $ processTikZs t
    applyKWs (SlideShare sid) = slideShare sid
    

As you can see adding another Keyword is easy and so after I was done with the video snippets I added SlideShare embedding.

The idea and implementation is obviously heavily influenced by Hakyll’s own Template compiler, except maybe from slight changes in the writing phase.

So once again hooray for open source software.

The only thing remaining is to add applyKeywords to your compiler stage like this:

match "posts/*" $ do
        route $ idRoute
        compile $ do
            pandocCompiler <$> applyKeywords
            >>= loadAndApplyTemplate "templates/post.html" (taggedPostCtx tags)
            >>= loadAndApplyTemplate "templates/main.html" (taggedPostCtx tags)

(Disclaimer: I added more stuff to the compiler so you won’t find these exact lines in the code of this page)

Show me the code

I’d really like you to build on this. And if you have additional Keywords you like to add, just do so and drop me a line, maybe they are useful for me too.

If you are interested, you can find the parsing stage here and writing out stage here. As well as the whole code of this blog in the surrounding github repository.

And now go and have fun with it.