knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
library("dovetail")

Introduction

The purpose of the dovetail package is to provide a way for Carpentries lesson contributors to write challenge and solution blocks in a simple and straightforward manner. We have taken inspiration from the Roxygen2 style of writing documentation above code blocks. For example, here is a function with documentation written above it with minimal markup:

#' Add two numbers
#'
#' @param x a number
#' @param y a number
#' @return the sum of x and y
#'
#' @export
#' @examples
#' add(1, 2) # 3
#' add(2, 1)
add <- function(x, y) {
  x + y
}

In the same vein, we could use this sort of syntax to create our own solution for generating special callout blocks within the Carpentries lessons:

```{challenge, eval = FALSE, coo = TRUE}

' ## Challenge 2

'

' Given the following code:

' ```r

x <- c(5.4, 6.2, 7.1, 4.8, 7.5) names(x) <- c('a', 'b', 'c', 'd', 'e') print(x)

' ```

'

' Write a subsetting command to return the values in x that are greater than 4 and less than 7.

'

' @solution Solution to challenge 2

'

```r

x_subset <- x[x<7 & x>4] print(x_subset)

' ```

## Motivation

The benefit of this is method that it's legible without conversion. Our
motivation for writing this stems from the way challenge blocks in lessons are
constructed via block quotes. While this is fine for regular text, it can be
challenging to write code examples AND ensure that they will render correctly.
Consider the following block quote (taken from episode 6 of R novice gapminder)
that gives a challenge block with a single solution block nested within it.
Within each of these blocks is a code block with R code that should be evaluated.

Challenge 2

Given the following code:

r x <- c(5.4, 6.2, 7.1, 4.8, 7.5) names(x) <- c('a', 'b', 'c', 'd', 'e') print(x)

Write a subsetting command to return the values in x that are greater than 4 and less than 7.

Solution to challenge 2

r x_subset <- x[x<7 & x>4] print(x_subset) {: .solution} {: .challenge}

When this gets processed by Jekyll, the kramdown tags (`{: .soltuion}`, `{: .challenge}`) are replaced by tags whose underlying css describes how the 
elements should be displayed. Here is what the resulting HTML would look like:

````html
<blockquote class="challenge">

  <h2 id="challenge-2">Challenge 2</h2>

  <p>Given the following code:</p>

  <!-- SNIP: highlighted R code and output -->

  <p>Write a subsetting command to return the values in x that are greater than 4 and less than 7.</p>

  <blockquote class="solution">
    <h2 id="solution-to-challenge-2">Solution to challenge 2<span class="fold-unfold glyphicon glyphicon-collapse-down"></span></h2>

    <!-- SNIP: highlighted R code and output -->

  </blockquote>
</blockquote>
````

The nesting here can be a significant distraction for contributors to lesson
templates because of a few reasons:

1. if you don't have the spacing correct, Jekyll throws strange errors
2. syntax highlighting is not available within block quotes
3. evaluation of code within a block quote involves manual copying and pasting

Of course, it's the two tags at the bottom that are doing *a lot* of the work in
conjunction with the kramdown parser employed by Jekyll. 

The idea behind this package is to write an engine for knitr that will parse the
above code block to look like this:


```{challenge "245:1-264:14", coo = TRUE, echo = TRUE}
#' ## Challenge 2
#' 
#' Given the following code:
#' ```r
x <- c(5.4, 6.2, 7.1, 4.8, 7.5)
names(x) <- c('a', 'b', 'c', 'd', 'e')
print(x)
#' ```
#'
#' Write a subsetting command to return the values in x that are greater than 4 and less than 7.
#'
#' @solution Solution to challenge 2
#'
#' ```r
x_subset <- x[x<7 & x>4]
print(x_subset)
#' ```

Specifications

At the moment, we are writing this to parse code chunks within RMarkdown documents, with the emphasis on R chunks. Because support for python exists via {reticulate}, it should be possible to extend this to Python, but the initial proof of concept will focus on R.

Importantly, we are not seeking to introduce novel concepts; we want to use existing terminology and tokens to make writing these blocks less complex

Chunk specifications

Tag specifications

The Carpentries lessons support the following tags to open div tags within the code block: r paste0("#' @", dovetail:::OUR_TAGS[dovetail:::OUR_TAGS != "end"]). Unless preceded by #' @end, each successive div tag will be nested within the previous tag. These tags behave by the following rules:

Processing

If we are going via a knitr engine route, then we need to apply our function for handling the engine via knitr::knit_engines$set(). What happens inside of that engine is ultimately up to us.

The steps we take are:

  1. Register one engine per tag with knitr::engines$set()
  2. chunks are labeled with the block label
  3. the engine parses the value in options$code via parse_block()
  4. The output is sent to knitr::knit() and the output of that is returned.


carpentries/dovetail documentation built on Sept. 23, 2021, 9:35 p.m.