Motivation: I want to write an interface that uses questions from the R package exams in learnr questions/quizzes. In R/exams each question is either an R/Markdown (Rmd) or R/LaTeX (Rnw) file with a certain structure specifying question, solution, and further meta-information. The questions can contain R code to make them dynamic, e.g., sampling numbers or certain text building blocks etc. Hence, the workflow is that first the questions are run through knitr::knit
or utils::Sweave
and then embedded in a suitable output format.
Problem: When I rmarkdown::run("learnr+rexams.Rmd")
a learnr tutorial that dynamically produces a question or quiz from an Rmd exercise I get the error:
Error in if (grepl(not_valid_char_regex, label)) { : argument is of length zero
The code for a simple reproducible example learnr+rexams.Rmd
is included below.
The reason for the error appears to be that learnr runs a function verify_tutorial_chunk_label()
that tries to assure the the learnr R chunk labels are well formatted. However, confusion is caused by the chunks that are run by the R/exams package, unnecessarily leading to the error above.
Workarounds: I can disable the verify_tutorial_chunk_label()
in the learnr namespace and then everything works well. Or I can use Rnw instead of Rmd exercises and then learnr does not conflict with Sweave()
. Also, when I run my code outside of a learnr tutorial it works fine.
Question: Can I do anything less invasive to make exams
cooperate with learnr
? For example, setting some appropriate knitr
options or something like that?
Example: This is the source for the minimal learnr tutorial learnr+rexams.Rmd
that replicates the problem. Note that everything is very much simplified and only works for certain R/exams exercises, here using the function exercise template that ships with R/exams.
---
title: "learnr & R/exams"
output: learnr::tutorial
runtime: shiny_prerendered
---
```{r exams2learnr, include = FALSE}
exams2learnr <- function(file) {
x <- exams::xexams(file)[[1]][[1]]
x <- list(text = x$question, type = "learnr_text",
learnr::answer(x$metainfo$solution, correct = TRUE))
do.call(learnr::question, x)
}
## assignInNamespace("verify_tutorial_chunk_label", function() return(), ns = "learnr")
```
```{r rfunctions, echo = FALSE, message = FALSE}
exams2learnr("function.Rmd")
```
Running this tutorial (as noted above) replicates the error. To avoid it I can either uncomment the assignInNamespace()
call or alternatively replace "function.Rmd"
by "function.Rnw"
.
The problem is that by the time learnr::question()
is called, knitr is no longer able to find the chunk label for the chunk where exams2learnr()
was called. You can get around this by setting the current chunk label before calling do.call(learnr_question, x)
:
exams2learnr <- function(file, label = knitr::opts_current$get("label")) {
force(label)
x <- exams::xexams(file)[[1]][[1]]
x <- list(
text = x$question,
type = "learnr_text",
learnr::answer(x$metainfo$solution, correct = TRUE)
)
knitr::opts_current$set(label = label)
do.call(learnr::question, x)
}
This also lets you set the label
dynamically if you want, which becomes the ID of the question in learnr.