Thursday, April 23, 2020 From rOpenSci (https://ropensci.org/blog/2020/04/23/rmd-learnings/). Except where otherwise noted, content on this site is licensed under the CC-BY license.
Whilst working on the blog guide, Stefanie Butland and I consolidated knowledge we had already gained, but it was also the opportunity to up our Rmd/Hugo technical game. Our website uses Hugo but not blogdown1 to render posts: every post is based on an .md file that is either written directly or knit from an .Rmd file. We wanted to provide clear guidance for both options, and to stick to the well-documented Hugo way of e.g. inserting figures. We also wanted to provide post contributors with an as smooth as possible workflow to create a new post. Working on this mission, unsurprisingly we learned a lot, and why not share our newly acquired technical know-how? In this post I shall go through four things we learned about Rmd/Hugo, while trying to provide context about the why of our using them.
Problem: Hugo has a nice figure shortcode with options such as width. How do we make R Markdown output these shortcodes from a code chunk, instead of the usual figure syntax? I.e. how to get2
{{< figure src=“chunkname-1.png” alt=“alternative text please make it informative” caption=“this is what this image shows, write it here or in the paragraph after the image as you prefer” width=“300” >}}
not
![alternative text please make it informative ](chunkname-1.png)
to appear – as the result of a chunk producing a figure – in the .md
after knitting the .Rmd
?
I asked this question in the friendly French-speaking R Slack workspace3 and got an answer from both Romain Lesur and Christophe Dervieux: the solution was to use a knitr plot hook!
knitr hooks are “Customizable functions to run before / after a code chunk, tweak the output, and manipulate chunk options”.
In the setup chunk4 of the .Rmd
file, there should be this code
knitr::knit_hooks$set(
plot = function(x, options) {
hugoopts <- options$hugoopts
paste0(
"{", "{<figure src=", # the original code is simpler
# but here I need to escape the shortcode!
'"', x, '" ',
if (!is.null(hugoopts)) {
glue::glue_collapse(
glue::glue('{names(hugoopts)}="{hugoopts}"'),
sep = " "
)
},
">}}\n"
)
}
)
that reads options from the chunk, and uses options from the hugoopts
named list if it exists.
The chunk
```r
plot(1:10)
```
produces
{{< figure src=“chunkname-1.png” alt=“alternative text please make it informative” caption=“this is what this image shows, write it here or in the paragraph after the image as you prefer” width=“300” >}}
in the .md
file which is what we wanted.
Now, a bit later in our website journey, I had a quite similar question: Hugo has nice highlighting options for code fences. How to make R Markdown show R source code with such options? This time there was no need to ask anyone, searching the internet for the name of the right knitr hook was enough: our .Rmd
has to feature a knitr source hook. Read more on that highlighting chapter in another tech note.
Note that when writing a .md
post instead of knitting an .Rmd
file, authors can use Hugo syntax directly. And when adding figures in an .Rmd
file that come from say a stock photos website rather than a code chunk, authors can also use Hugo syntax, granted they write the shortcode between html_preserve
markers, see below the lines I used to add the crochet hook picture in the .Rmd
producing this post.
<!--html_preserve-->
{{< figure src =“person-holding-purple-crochet-hook-and-white-yarn-3945638.jpg” alt = “Person holding a purple crochet hook and white yarn” link = “https://www.pexels.com/photo/person-holding-purple-crochet-hook-and-white-yarn-3945638/" caption = “Castorly Stock on Pexels” width = “300” class = “center” >}}
<!--/html_preserve-->
Problem: Our advice to contributors including ourselves was to add their post under content/ but its images under themes/ropensci/static/img/ which is… not smooth. How do we change that?
Thanks to Alison Hill’s blog post about page bundles I learned you can use a folder to add both a post and its related images to a Hugo website. What an improvement over adding the post in one place, the images in another place like we used to! It’s much smoother to explain to new contributors5. In Hugo speak, each post source is a leaf bundle.
We did not have to “convert” old posts since both systems can peacefully coexist.
Below is the directory tree of this very tech note, added as a leaf bundle.
Warning in file.remove(file.path(path, "index.html")): impossible d'effacer le
fichier '/home/maelle/Documents/ropensci/WEBSITE/roweb3/content/blog/2020-04-23-
rmd-learnings/index.html', à cause de 'Aucun fichier ou dossier de ce type'
/home/maelle/Documents/ropensci/WEBSITE/roweb3/content/blog/2020-04-23-rmd-learnings
├── blogdownaddin.png
├── index.Rmd
├── index.Rmd.lock~
├── index.knit.md~
├── orange-mug-near-macbook-3219546.jpg
├── person-holding-purple-crochet-hook-and-white-yarn-3945638.jpg
└── richard-dykes-SPuHHjbSso8-unsplash.jpg
In R Markdown, in the setup chunk, the option fig.path
needs to be set to ""
via knitr::opts_chunk$set(fig.path = "")
.
Problem: We link to our two templates from the blog guide, and explain where under content the folder corresponding to a post should be created, but that leaves a lot of work to contributors.
How do we distribute our .Rmd
and .md
templates?
How do we help authors create a new post without too much clicking around and copy-pasting?6
After a bit of digging in Hugo docs and blogdown GitHub, reading a blog post of Alison Hill’s, contributing to blogdown in spontaneous collaboration with Garrick Aden-Buie, getting useful feedback from blogdown maintainer Yihui Xie… here’s the place I got to: storing templates as Hugo directory-based archetypes, and recommending the use of a handy blogdown RStudio addin to create new posts.
First of all, an important clarification: in Hugo speak, a template for Markdown content is called an archetype. A template refers to html layout. Not confusing at all, right?
We have stored both the templates archetypes for Markdown and R Markdown posts as directory based archetypes. The Rmd template is stored as index.md
, otherwise Hugo doesn’t recognize it (thanks Leonardo Collado-Torres). Posts created using the template should however be saved with the usual .Rmd extension.
After reading the explanations around options in the blogdown book, I added an .Rprofile to our website repository.
if (file.exists('~/.Rprofile')) {
base::sys.source('~/.Rprofile', envir = environment())
}
# All options below apply to posts created via the New Post Addin.
# to enforce leaf bundles:
options(blogdown.new_bundle = TRUE)
# to make blog the subdirectory for new posts by default:
options(blogdown.subdir = "blog")
# to help enforce our strict & pedantic style guide ;-)
options(blogdown.title_case = TRUE)
The blogdown New Post Addin assumes the author name can be stored in the “author” field of the post YAML metadata, whereas our website used to call that field “authors”… that called for a massive pull request, editing previous posts, and making our templating more resilient to having a single author as a string rather than a list. When editing the metadata of previous posts I like to use the yaml package. I made a few mistakes that were thankfully spotted and fixed by Steffi LaZerte.
Now from RStudio or elsewhere after installing blogdown above version 1.18.17, we recommend using the blogdown’s New Post Addin!
The todo list for a contributor is long but less tedious than creating folders by hand:
Enter a title, no need to worry about title case at this stage.
Enter your name if whoami wasn’t able to guess it.
Choose the correct date.
Enter a new slug if the default one is too long.
Choose “blog” or “technotes” as a Subdirectory from the drop-down menu.
Choose an Archetype, Rmd or md, from the drop-down menu.
Also choose the correct Format: .Rmd if Rmd, Markdown (.md) if md. Never choose .RMarkdown.
Ignore Categories.
Select any relevant tag and/or create new ones.
Click on “Done”, your post draft will have been created and opened.
The addin can also be used outside of RStudio.
If you don’t use that nice little tool, unless you use hugo new
yes you’d copy-paste from a file and create a correctly named folder yourself.
Problem: How do we make Hugo ignore useless html from our knitting process without deleting said html by hand?
At the moment the output type in our .Rmd
template archetype is
output:
html_document:
keep_md: yes
that produces a .md
like we need.
I hope we’ll find a better way one day, and welcome any cleaner suggestion for the output field (I tested a few Markdown variants without success!).
I have the perhaps naive impression that blogdown actually follows a similar process when knitting .RMarkdown
to .markdown
?
So anyway, we currently have an useless html produced when knitting a post from an .rmd
file, how do we make Hugo ignore it?
I knew about .gitignore
where we have the lines
content/blog/*/index.html
content/technotes/*/index.html
so that such garbage index.html doesn’t get added to pull requests, but it still made Hugo error locally.
Luckily Hugo recognizes a config field called ignoreFiles
.
Ours now mentions index.html.
ignoreFiles = ["\\.Rmd$", "_files$", "_cache$", "index\\.html"]
In this post I reported on a few things our website work taught us about R Markdown (knitr hooks), Hugo (ignoreFiles, leaf bundles, archetypes) and blogdown (New Post Addin). We’re still learning important concepts and tricks thanks to new questions by blog authors and to updates in the ecosystem8, we shall keep publishing such posts, stay tuned if that’s your jam!
But as you’ll see later we actually take advantage of that cool package: we recommend using blogdown’s New Post Addin; and we also mention blogdown::install_hugo()
in the blog guide. ↩︎
Yes you can escape Hugo shortcodes in your site content! ↩︎
If you want to join us, follow the invitation link. À bientôt ! ↩︎
I recently asked and received references to define the setup chunk. ↩︎
I can also credit this smoother workflow for making me like adding more images, hence the stock pictures in this post! ↩︎
Thanks to Steffi LaZerte for encouraging work in this direction! ↩︎
At the time of writing blogdown 1.18.1 has to be installed from GitHub via remotes::install_github("rstudio/blogdown")
. ↩︎
I actually wrote a post around maintaining Hugo websites in my personal blog. ↩︎