Combining Knitr and Jekyll took some effort. This post describes how to get it all working as part of a statistical blogging pipeline.

Introduction

If you’re reading this, you know that I write a blog about statistics for Inferentialist Consulting. You may also be aware of the knitr package, useful for reproducible research. knitr is a dramatic improvement over its predecessor, Sweave, and the easiest way I’ve found to interweave text and R output. However, until now, I’ve only used knitr in a latex workflow. Now that I’m authoring a blog—I use jekyll—I’d love a similarly easy way to transform R Markdown to jekyll Markdown.

The good news is that most of the plumbing already exists. RStudio has some functionality that exposes R Markdown in a jekyll format. However, the context requires running jekyll as a temporary background process. The missing piece is the ability to export transformed R Markdown into jekyll Markdown.

Luckily, it doesn’t take too much R magic to extend knitr in exactly the way we want. Here’s the code to do the job:

sample.Rmd

input_file = "sample.Rmd"

# Remove the file extension
base_name = gsub("(^.*)\\.(.*$)", "\\1", input_file, fixed=FALSE)

# Define the location of the blog; here, a soft link
blog_dir = "./blog"
posts_dir = paste(blog_dir, "_posts", sep="/")

# Define the output file,
output_basename = paste(base_name, "md", sep=".")
output_file = paste(posts_dir, output_basename, sep="/")

# ...and the output path for the figures
fig.path = paste(blog_dir, "assets", base_name, "", sep="/")

# Set up the hooks for a RStudio jekyll
knitr::render_jekyll()

# Maybe you want some nice defaults for the figures / images
knitr::opts_chunk$set(
    fig.width=6,
    fig.height=4.5,
    fig.path=fig.path,
    warning=FALSE,
    message=FALSE
    )

# Tweak the upload.fun hook so that the blog_dir is removed
# when the Markdown link is written out
knitr::opts_knit$set(
    upload.fun = function(x){
        gsub(blog_dir, "", x)
        ## _OR_  gsub(blog_dir, "http://blog.inferentialist.com", x)
    }
    )

# The default table attributes confuses jekyll, but this works
options(xtable.html.table.attributes = "class='foobar'")

# Finally, let knitr do its thing.
knit(input_file, output = output_file)
 

Extras

Verbatim Rmarkdown

The following extension adds a verbatim flag which is useful for showcasing the actual Rmd code.
I borrowed this idea from Ramnath Vaidyanathan with slight modifications.

source_orig = knitr::knit_hooks$get()$source
knitr::knit_hooks$set(
    source = function(x, options){
        if (!is.null(options$verbatim) && options$verbatim){
            opts = gsub(",\\s*verbatim\\s*=\\s*TRUE\\s*", "", options$params.src)
            opts = gsub(",\\s*eval\\s*=\\s*FALSE\\s*", "", opts)
            bef = sprintf('\n\n    ```{r %s}\n', opts, "\n")
            stringr::str_c(
                bef, 
                knitr:::indent_block(paste(x, collapse = '\n'), "    "), 
                "\n    ```\n\n"
                )
        } else {
            source_orig(x, options)
        }
    })
 

Debugging Knitr

Finally, it can be useful to add debug wrappers around the hooks. For example, this lets you know which hooks are actually called and with what parameters.

fdbg = list()
for(item in names(knitr::knit_hooks$get())){
    fdbg[[item]] = knit_hooks$get(item)

    ## Create a new environment so that the function is invoked
    ## with the correct value of item
    e = new.env()
    e$item = item
    fn = function(...) {            
        cat("----------", item, "\n")
        flush.console()
        browser()
        fdbg[[item]](...)
    }
    environment(fn) = e

    ## The setter doesn't quite work the way you'd expect, so
    ## use reflection.
    cmd = paste("knit_hooks$set(",item," = fn)", sep="")
    eval(parse(text=cmd))
}