Create and use a custom roxygen2 tag

[This article was first published on Maëlle's R blog on Maëlle Salmon's personal website, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

You might know that it’s possible to extend roxygen2 to do all sorts of cool things including but not limited to:

I did something much more basic but still challenging to me: creating a custom tag that adds a new section to a manual page. In this post I’ll summarize how to go about that.

Context: need for a custom tag in the igraph R package

The igraph R package outsources a lot of its work to the igraph C library. Sometimes, the R package docs therefore have to try and catch up with the C library docs. One idea we’ve had when thinking about the problem is to link to relevant C docs from each R manual page: if a function called igraph::empty_graph() uses igraph_empty under the hood, then its manual page should link to https://igraph.org/c/doc/igraph-Basic.html#igraph_empty. It means the users would be able to easily browse the C docs, and as a side-effect they’d be aware of what part of the C library supports what functionality in the R package.

A workflow to make that happen is the following:

  • have a sitemap of the C docs at hand (we parse the index page of the C docs);
  • add @cdocs tags in the source of the R package, for instance @cdocs igraph_empty near the function definition (adding them semi-automatically will be a topic for another day 😅);
  • have some roxygen2 machinery parsing the @cdocs into links in the manual page when we call document().

This blog post is about the roxygen2 machinery as I had to piece together several things I saw online.

Minimal example

Create our minimal R package

R package hgb

The hgb package has one script whose docs use a custom @custom tag:

#' A function messaging a thing
#'
#' @custom Hello I am here
#'
#' @return Nothing, print thing
#' @export
#'
#' @examples
#' thing()
thing <- function() {
  message("thing")
}

Create a package holding the custom tag

R package myroxy

In a script I have the following lines that mostly come from an roxygen2 vignette, in which the creation of a custom tag is well documented (its usage less so 😅).

There’s a method roxy_tag_parse.roxy_tag_custom() telling roxygen2 how to parse the tag, a method roxy_tag_rd.roxy_tag_custom telling roxygen2 what to do with the value: a special “custom” section. Then there’s a method format.rd_section_custom() telling roxygen2 how to build the “custom” section. Here it’s just pasting. In my real use case I defined a helper function to identify the C docs link corresponding to a given tag value.

#' @importFrom roxygen2 roxy_tag_parse
#' @importFrom roxygen2 roxy_tag_rd
NULL

#' @export
# same as https://roxygen2.r-lib.org/articles/extending.html
roxy_tag_parse.roxy_tag_custom <- function(x) {
  roxygen2::tag_markdown(x)
}

#' @export
# same as https://roxygen2.r-lib.org/articles/extending.html
roxy_tag_rd.roxy_tag_custom <- function(x, base_path, env) {
  roxygen2::rd_section("custom", x$val)
}

#' @export
# same as https://roxygen2.r-lib.org/articles/extending.html
format.rd_section_custom <- function(x, ...) {
  paste0(
    "\\section{Custom section}{\n",
    "\\itemize{\n",
    paste0("  \\item ", x$value, "\n", collapse = ""),
    "}\n",
    "}\n"
  )
}

Create a roclet for sharing the custom tag

Even if all we do is creating a custom roxygen2 tag, we need to define a roclet as it’ll be the vessel by which another package can use the custom tag. You can have a custom tag defined inside a package without a roclet if you use the custom tag within the same package. But to cross package boundaries you need a roclet.

Further in the same script I have those lines:

#' @export
# https://github.com/shahronak47/informationtag
custom_roclet <- function() {
  roxygen2::roclet("custom")
}

#' @importFrom roxygen2 block_get_tags roclet_process
#' @method roclet_process roclet_custom
#' @export
# https://github.com/shahronak47/informationtag
roclet_process.roclet_custom <- function(x, blocks, env, base_path) {
  x
}


#' @export
#' @importFrom roxygen2 block_get_tags roclet_output
roclet_output.roclet_custom <- function(x, results, base_path, ...) {
  x
}

You’ll notice we defined something called “custom_roclet” but now the methods are for “roclet_custom” (inverted word order). This confused me at first. Furthermore, I had no idea what the process and output methods were supposed to do, so I used the demo by Ronak Shah. Lastly, the roxygen2 tags used in that demo didn’t work for me so I stole the ones used by Antoine Fabri in devtag.

Build the package holding the custom tag

By that I mean running document() and installing the myroxy package on my machine to try it.

Register the roclet in our minimal package

I added the following lines to the DESCRIPTION of the hgb package to indicate to roxygen2 that there’s another package to use when documenting my package:

Roxygen: list(markdown = TRUE, roclets = c("collate", "rd", "namespace",
    "myroxy::custom_roclet"), packages = "myroxy")

I also added these lines so that fellow developers might know where to get myroxy from1:

Config/Needs/build: roxygen2, devtools, maelle/myroxy

Try it

If all goes well, running document() in the hgb package works without any error. The manual page for ?thing has the custom section.

Do I need an external package?

The roxygen2 extensions I mentioned in the introduction (devtag, srr, roxytag) are standalone packages that you can use when building your own package, because their use case is fairly general. Here, I am developing a custom tag for igraph only. So in theory, the infrastructure could live inside the R package itself.

I actually started by implementing the custom tag within the igraph package but I was not a fan of the clutter it created in data-raw, R, the NAMESPACE, etc. It was great to start this way as it was much easier (no roclet!), but I ended up factoring out the roxygen2 code in a distinct GitHub-only package called igraph.r2cdocs.

Sources

I am very thankful to the authors of roxygen2 obviously, and to the authors of the following resources:

  • roxygen2 vignette “Extending roxygen2” that says of itself that it’s “very rough”. For me the missing part was understanding what goes in what package (the package where you define the tags, the package where you use the tags); and I’d have obviously liked some explanations on roclets that are just there for a basic custom tag, as opposed to printing content / saving information in other files. I want docs just for me. 😉
  • Stack Overflow post
  • Video by Ronak Shah that I didn’t watch and related repositories that I did stare at a lot: informationtag and infotagdemo. This is how I understood what the “process” and “output” methods of my roclet had to look like.
  • devtag source which is where I got the necessary roxygen2 tags for the “process” and “output” methods of my roclet (the ones from the aforementioned demo didn’t work for me).

Conclusion

In this post I explained the creation and usage of a custom roxygen2 tag, with a minimal example. If you’re curious about the igraph use case, find the demo PR to igraph and the igraph.r2cdocs package. Have you ever extended roxygen2? What were the difficulties in your experience?


  1. This is a custom… DESCRIPTION field. ↩︎

To leave a comment for the author, please follow the link and comment on their blog: Maëlle's R blog on Maëlle Salmon's personal website.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)