Search code examples
rroxygen2roxygen

How to run documentation functions inside roxygen2 @examples?


I have numerous similar functions where only one or two words need to change in the documentation. To limit duplication, I'm using functions inside the documentation.

This works in roxygen2 for @description and @param, but I cannot get it to work for @examples. The inline code is not evaluated.

Example

useless_description <- function(name) {
  paste0('This useless function prints the word "', name , '" n times.')
}

useless_example <- function(name) {
  paste("# Print ", name, " 1 time",
        name, "(1)",
        "",
        "# Print ", name, " 3 times",
        name, "(3)",
        sep = "\n"
        )
}

#' @title Hello
#' @description `r useless_description("hello")`
#' @examples `r useless_example("hello")`
hello <- function(n) {
  print(rep("hello", n))
}

#' @title Goodbye
#' @description `r useless_description("goodbye")`
#' @examples `r useless_example("hello")`
goodbye <- function(n) {
  print(rep("goodbye", n))
}

When running devtools::document(), this is the help page that is created for the function hello():

enter image description here

Possible solution using @eval

I have found a way around this, but it uses the superseded keyword @eval.

useless_example2 <- function(name) {
  c("@examples ",
    paste0("# Print ", name, " 1 time"),
    paste0(name, "(1)"),
    "",
    paste0("# Print ", name, " 3 time"),
    paste0(name, "(3)")
    )
}

#' @title Hello
#' @description `r useless_description("hello")`
#' @eval useless_example2("hello")
hello <- function(n) {
  print(rep("hello", n))
}

Resulting help page:

enter image description here

Question

Is there a way to evaluate inline code in roxygen2 @examples without using superseded functions?


Solution

  • First note that the use of inline code with {Roxygen2} only works if markdown support is turned on. That is accomplished either:

    1. globally by inserting Roxygen: list(markdown = TRUE) into the package DESCRIPTION file (e.g., usethis::use_roxygen_md()) or
    2. locally by using @md in the present roxygen block

    As you noted, the use of @eval has been superseded in favor of inline R code.

    On that same page, the authors go on to say:

    Inline R markdown can only generate markdown text within a tag so in principle it is less flexible than @eval/@evalRd/@template. However, our experience has revealed that generating multiple tags at once tends to be rather inflexible, and you often end up refactoring into smaller pieces so we don’t believe this reflects a real loss of functionality.

    I see two points worth repeating:

    1. Inline R markdown/code can only generate on a per tag basis.
    2. The authors are not interested in changing this behavior.

    Then on this page the authors note that:

    Some of the roxygen tags are not parsed as markdown. Most of these are unlikely to contain text that needs markup, so this is not an important restriction. Tags without markdown support: @aliases, @backref, @docType, @encoding, @evalRd, @example, @examples, @family, @inheritParams, @keywords, @method @name, @md, @noMd, @noRd, @rdname, @rawRd, @usage.

    This is why your inline code works for the @description, but does not work for @examples as the package authors have explicitly decided not to support inline code for @examples.

    If you wish to follow along with the package authors' intent and not use deprecated functionality, I suggest you extend {roxygen2} with your own custom tag. You can even make it so that your tag simply adds to the already existing examples section.

    While there would be kinks to work out, adding your own custom tag can be as simple as defining two functions like this:

    roxy_tag_parse.roxy_tag_examples2 <- function(x) {
      roxygen2::tag_markdown(x)
    }
    
    roxy_tag_rd.roxy_tag_examples2 <- function(x, base_path, env) {
      roxygen2::rd_section("examples", x$val)
    }
    

    Then load these functions into memory before documenting your package.

    Here is an example R package file:

    roxygen_generate_description <- function(x) {
      sprintf('This function prints the word "%s" n times.', x)
    }
    
    # a kink to workout:
    # - use \u0023 for # to avoid an attempt at a level 1 header
    roxygen_generate_examples <- function(x) {
      paste(
        sprintf("\u0023 Print %s 1 time", x),
        sprintf("%s(1)", x),
        "",
        sprintf("\u0023 Print %s 3 times", x),
        sprintf("%s(3)", x),
        sep = "\n"
      )
    }
    
    #' @title Hello
    #' @description `r roxygen_generate_description("hello")`
    #' @examples2 `r roxygen_generate_examples("hello")`
    hello <- function(n) {
      print(rep("hello", n))
    }
    

    And then doing:

    devtools::document()
    ?hello
    

    We get this:

    enter image description here