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
- Blog with Knitr and Jekyll
- Jekyll and knitr
- Using (R) Markdown, Jekyll, & GitHub for a Website
- Build websites with R and nanoc or Jekyll
- dfeng.github.com/make.R
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.