After getting a barebones Jekyll install up and running, my next step was to work out how to post R Markdown files to it. There is nothing wrong with regular markdown, but after using R for several years and LaTeX for many more years, I preferred the larger set of possibilities that come with R Markdown.

There are two major routes to work with R Markdown source files with Jekyll, one involves using the Jekyll plugins system, and the other uses R (in effect the knitr::render_jekyll() function) to pre-process all R Markdown files to Jekyll-compatible markdown files.

The plugins route appeared quite convoluted, and being unfamiliar with Ruby, gems, etc. did not make my situation any better. I found a few pages describing the process, but the temptation of using more familiar tools was too great.

R (knitr) pre-process route

I used all of the listed pages for inspiration, but I based my solution heavily on dfeng’s make.R script.

In my Jekyll site source directory, I created a _knitr directory, which will hold R Markdown files. In the same directory, I placed my Rmd -> md conversion script (called render_post.R).

#!/usr/bin/env Rscript
options(stringsAsFactors = FALSE)

# inspiration sources:
# http://www.jonzelner.net/jekyll/knitr/r/2014/07/02/autogen-knitr/
# http://gtog.github.io/workflow/2013/06/12/rmarkdown-to-rbloggers/

KnitPost <- function(bashwd = "", convert_file = "", overwrite = FALSE) {
   # CONVERT ALL RMD FILES TO MARKDOWN?
   #    REQUIRED: overwrite
   # CONVERT A SPECIFIC RMD FILE TO MARKDOWN?
   #    REQUIRED: bashwd, convert_file

   # bashwd: working directory (passed on from bash, only used below
   #         if a specific Rmd file is to be converted)
   # convert_file: name/path to specific Rmd file to convert
   # overwrite: flag that tells us whether to overwrite md files
   #            when converting all Rmd files

   # directory of jekyll blog (including trailing slash)
   site.path <- "/home/taha/jekyll/chepec/"
   # directory where your Rmd-files reside (relative to base)
   rmd.path <- paste0(site.path, "_knitr")
   # directory to save figures
   fig.dir <- "figures/"
   # directory for converted markdown files
   posts.path <- paste0(site.path, "_posts")
   # cache
   cache.path <- paste0(site.path, "_cache")

   require(knitr)
   render_jekyll(highlight = "pygments")
   # "base.dir is never used when composing the URL of the figures; it is
   # only used to save the figures to a different directory, which can
   # be useful when you do not want the figures to be saved under the
   # current working directory.
   # The URL of an image is always base.url + fig.path"
   # https://groups.google.com/forum/#!topic/knitr/18aXpOmsumQ
   opts_knit$set(
      base.url = "/",
      base.dir = site.path)
   opts_chunk$set(
      fig.path   = fig.dir,
      fig.width  = 8.5,
      fig.height = 5.25,
      dev        = 'svg',
      cache      = FALSE,
      warning    = FALSE,
      message    = FALSE,
      cache.path = cache.path,
      tidy       = FALSE)


   if (convert_file == "") {
      # convert all Rmd files in {site-url}/_knitr/ to markdown files
      # contingent on whether overwrite requested and if md exists

      # setwd to Rmd folder
      setwd(rmd.path)

      files.rmd <-
         data.frame(rmd = list.files(
            path = rmd.path,
            full.names = TRUE,
            pattern = "\\.Rmd$",
            ignore.case = TRUE,
            recursive = FALSE))

      files.rmd$corresponding.md.file <-
         paste0(posts.path, "/",
            basename(gsub(pattern = "\\.Rmd$",
               replacement = ".md",
               x = files.rmd$rmd)))

      files.rmd$corresponding.md.exists <-
         file.exists(files.rmd$corresponding.md.file)
      files.rmd$md.overwrite <- overwrite
      files.rmd$md.render <- FALSE
      # check if corresponding md file exists for each Rmd file,
      # if not, set flag to convert to markdown
      # (also consider the overwrite flag set by user)
      for (i in 1:dim(files.rmd)[1]) {
         if (files.rmd$corresponding.md.exists[i] == FALSE) {
            files.rmd$md.render[i] <- TRUE
         }
         if ((files.rmd$corresponding.md.exists[i] == TRUE) &&
             (files.rmd$md.overwrite[i] == TRUE)) {
            files.rmd$md.render[i] <- TRUE
         }
      }

      # For each Rmd file, render markdown (contingent on the flags set above)
      for (i in 1:dim(files.rmd)[1]) {
         # only re-knit if overwrite==TRUE or .md not already existing
         if (files.rmd$md.render[i] == TRUE) {
            # KNITTING ====
            message(paste0("=== KnitPost(overwrite=", overwrite, "): ",
                           basename(files.rmd$rmd[i])))
            out.file <-
               knit(files.rmd$rmd[i],
                  output = files.rmd$corresponding.md.file[i],
                  envir = parent.frame(),
                  quiet = TRUE)
         }
      }
   } else {
      # convert a single Rmd file to markdown
      # setwd to bash pwd
      setwd(bashwd)
      convert.path <- paste0(bashwd, "/", convert_file)
      md.path <-
         paste0(posts.path, "/",
         basename(gsub(pattern = "\\.Rmd$",
            replacement = ".md",
            x = convert_file)))
      # KNITTING ====
      message(paste0("=== KnitPost(", convert.path, ")"))
      out.file <-
         knit(convert.path,
            output = md.path,
            envir = parent.frame(),
            quiet = TRUE)
   }
}

Now we just need to run this script whenever we have new or updated Rmd-files in the _knitr directory. Since I already created a bash script that handles the jekyll build command, I decided to handle the R script in the same place.

#!/bin/bash

function show_help {
   echo "Usage: buildsite.sh [OPTION]..."
    echo "Knit posts, rebuild jekyll blog <chepec>"
    echo ""
    echo "-c   convert all _knitr/*.Rmd files to _posts/*.md (does not overwrite existing md)"
    echo "-o   overwrite existing *.md files"
   echo "-f   convert '~/jekyll/chepec/_knitr/<filename>.Rmd' to md in _posts"
    echo "-b   build the jekyll site"
    echo "-s   send changes to live dir"
    echo "-h   show this help"
}

rmdfile=""
bashpwd=$(pwd)

# initialise flags here
# convert new *.Rmd => *.md?
convert=false
# convert even if *.md exists?
overwrite_md=false
# convert a specific Rmd file?
convert_file=false
# build jekyll site?
buildsite=false
# make changes live?
send_live=false

if [ $# -eq 0 ] ; then
   # no args at all? show usage
   show_help
   exit 0
fi

# reset OPTINDEX in case getopts has been used previously in the shell
OPTIND=1
while getopts "h?cf:obs" opt; do
    case $opt in
        c)
            # just a flag, no options
            convert=true
            ;;
      f)
         # has a mandatory option, filename
         convert_file=true
         rmdfile=$OPTARG
         ;;
        o)
            # just a flag, no options
            overwrite_md=true
            ;;
        b)
            # just a flag, no options
            buildsite=true
            ;;
      s)
            # just a flag, no options
            send_live=true
            ;;
        h|\?)
            show_help
            exit 0
    esac
done
shift $((OPTIND-1))

#### 1. convert/overwrite *.Rmd to *.md
# uses the function KnitPost() from the R-script render_post.R
if [ "$convert" = true ] ; then
    echo ">>>> jc: Converting RMarkdown to Markdown (overwrite = $overwrite_md)"
    if [ "$overwrite_md" = true ] ; then
        Rscript -e "source('~/jekyll/chepec/_knitr/render_post.R'); KnitPost(overwrite=TRUE)"
    else
        Rscript -e "source('~/jekyll/chepec/_knitr/render_post.R'); KnitPost()"
    fi
fi

#### 2. convert a specific Rmd-file
# uses the function KnitPost() from the R-script render_post.R
if [ "$convert_file" = true ] ; then
   # check that $rmdfile exists
   if [ -f $rmdfile ]; then
      Rscript -e "source('~/jekyll/chepec/_knitr/render_post.R'); KnitPost(bashwd='$bashpwd',convert_file='$rmdfile')"
   else
      show_help
      exit 0
   fi
fi

#### 3. build the site
if [ "$buildsite" = true ] ; then
    echo ">>>> jc: Building site to /var/www/blog"
    jekyll build -s ~/jekyll/chepec -d /var/www/blog
fi

#### 4. make changes live (send to public server)
if [ "$send_live" = true ] ; then
    echo ">>>> jc: Sending changes to live server"
    unison jekyll-damietta -auto
fi

exit 0

This way, we keep the Jekyll source directory exclusively on the local machine, and only sync the site directory with the remote machine (the public-facing server). We still need to maintain a Jekyll install on both the local and remote machine, though – haven’t found a way around that. Correction: we only require a Jekyll stack on the machine where we build the site. Once Jekyll has built the site, the result is just a bunch of html, css and js that can be served to almost any browser.