9.10 Option hooks

Sometimes you may want to change certain chunk options dynamically according to the values of other chunk options, and you may use the object opts_hooks to set up an option hook to do it. An option hook is a function associated with the option and to be executed when a corresponding chunk option is not NULL. For example, we can tweak the fig.width option so that it is always no smaller than fig.height:

knitr::opts_hooks$set(fig.width = function(options) {
  if (options$fig.width < options$fig.height) {
    options$fig.width <- options$fig.height
  }
  options
})

Because fig.width will never be NULL, this hook function is always executed before a code chunk to update its chunk options. For the code chunk below, the actual value of fig.width will be 6 instead of the initial 5 if the above option hook has been set up:

```{r fig.width = 5, fig.height = 6}
plot(1:10)
```

As another example, we rewrite the last example in Section 9.7 so we can use a single chunk option console = TRUE to imply comment = "" and prompt = TRUE. Note that console is not a built-in knitr chunk option but a custom and arbitrary option name instead. Its default value will be NULL. Below is a full example:

```{r, include=FALSE}
knitr::opts_hooks$set(console = function(options) {
  if (isTRUE(options$console)) {
    options$comment <- ''; options$prompt <- TRUE
  }
  options
})
```

Default output:

```{r}
1 + 1
if (TRUE) {
  2 + 2
}
```

Output with `console = TRUE`:

```{r, console=TRUE}
1 + 1
if (TRUE) {
  2 + 2
}
```

The third example is about how to automatically add line numbers to any output blocks, including source code blocks, text output, messages, warnings, and errors. We have mentioned in Section 4.3 how to use chunk options such as attr.source and attr.output to add line numbers. Here we want to use a single chunk option (numberLines in this example) to control the blocks to which we want to add line numbers.

# a function to generate option hooks
numberLines <- function(code = c("source", "output", "message", 
  "warning", "error")) {
  .code <- match.arg(code)
  .attr <- paste0("attr.", code)
  
  function(options) {
    if (.code %in% options$numberLines) {
      options[[.attr]] <- c(options[[.attr]], ".numberLines")
    }
    options
  }
}

# set option hooks
knitr::opts_hooks$set(attr.source = numberLines("source"), 
  attr.output = numberLines("output"), attr.message = numberLines("message"), 
  attr.warning = numberLines("warning"), attr.error = numberLines("error"))

# automatically run hooks by setting attr.* to empty
# string (or any value but `NULL`)
knitr::opts_chunk$set(attr.source = "", attr.output = "", 
  attr.message = "", attr.warning = "", attr.error = "", 
  numberLines = c("source", "output", "message", "warning", 
    "error"))

Basically, the option hooks append the attribute .numberLines to output blocks, and the chunk options set via opts_chunk$set() make sure that these options hooks will be executed.

With the above setup, you can use the chunk option numberLines on a code chunk to decide which of its output blocks will have line numbers, e.g., numberLines = c('source', 'output'). Specifying numberLines = NULL removes line numbers completely.

You may wonder why not setting the chunk options directly, e.g., just knitr::opts_chunk$set(attr.source = '.numberLines') like we did in Section 4.3. The advantage of using the option hooks here is that they only append the attribute .numberLines to chunk options, which means they will not override existing chunk option values, e.g., the source code block of the chunk below will be numbered (with the above setup), and the numbers start from the second line:

```{r, attr.source='startFrom="2"'}
# this comment line will not be numbered
1 + 1
```

It is equivalent to:

```{r, attr.source=c('startFrom="2"', '.numberLines'}
# this comment line will not be numbered
1 + 1
```