Easiest way to create commandline versions of R functions already documented in a package

Say I'm developing an R package, and have a function 'add', documented with Roxygen comments

#' Simple Addition
#'
#' Casts arguments to numeric, adds them together, then prints results
#'
#' @param a first number
#' @param b second number
#'
#' @return sum of a and b
#' @export
#'
#' @examples
#' add(2+2) # returns 4
add <- function(a, b){
  a <- as.numeric(a)
  b <- as.numeric(b)

 return(a+b)
}

What is the easiest way to create a commandline interface (with sensible usage / help messages) for this function. Preferably these commandline interfaces would be distributed in the same R package as the functions they wrap.

Current Solution
I've played around with both optparse and docopt and think both are really great. My current solution is to create a separate CLI scripts/functions that calls the non-CLI functions (i store these in an inst/ dir so distributed with package). The big downside is I end up duplicating a lot of text documenting the CLI wrapper. For the above example, I might make a docopt as follows:

library(docopt)
library(package_containing_add_function)

'Simple Addition

Description:
  Casts arguments to numeric, adds them together, then prints results

Usage:
  add <a> <b>

Returns:
  sum of a and b

Example:
  add 2 2 --> returns 4

' -> doc

arguments <- docopt(doc)

add(as.numeric(arguments$a), as.numeric(arguments$b))

To do this for lots of functions, especially as the number of arguments increases, is time consuming and tricky to maintain (there are 2 sets of nearly identical text to maintain in order to get good help messages). Is there a more efficient way to create commandline bindings from documented package functions where I do less duplication of documentation for but get similar results (useful help/usage messages)?

I also see GitHub - devOpifex/cmd: Code generator to produce CLI from R packages by @JohnCoene but am not sure it handles docs.

1 Like

How about making a wrapper function that would parse the RD files and create a doc string?
This will still need some cleaning up, I think, but as a start it could look something like this

rd_to_doc <- function(path){
  path <- normalizePath(path)
  x <- capture.output(tools::Rd2txt(path))
  x <- gsub("_\b", "", x)
  paste0(x, collapse="\n")
}

rd_to_doc("man/lcbc_cols.Rd")

rd_to_doc("man/lcbc_cols.Rd")
[1] "Function to extract lcbc colors as hex codes\n\nDescription:\n\n     Function to extract lcbc colors as hex codes\n\nUsage:\n\n     lcbc_cols(...)\n     \nArguments:\n\n     ...: Character names of lcbc_colors\n"

Created on 2022-05-18 by the reprex package (v2.0.1)

I've not tested how this would look from command line though, might need to do some text-magic to make it render nicely, but might be worth while to explore?

2 Likes

tools::Rd2txt_options() might also contain some options to make this easier

2 Likes

Thanks for your response @maelle. I had not heard about cmd before. I like the idea of defining commandline versions simply by describing options/arguments in a reasonably simple json file. Only downside is it doesn't appear to support automatic transfer of roxygen documentation

1 Like

Thanks @DrMowinckels. I think making a wrapper function will probably be the way to go. Thanks for the example code! I did not know about the tools::Rd2txt function - definitely makes building this out easier! When I have some free time I think I'll start fleshing out a function like yours that automates the process. Would be nice to have something that knows whether the result is reasonably likely to work with packages like docopt at the time (e.g. having a basic docopt docstring parser attached).

Not 100% sure at this point whether I'll build it out for docopt/cmd/optparse (thanks @maelle for bringing cmd to my attention). This is a problem for future me :slight_smile:

Since a custom wrapper seems to be required to solve my exact problem - I'll mark your response as the solution.

For future souls looking for the code I end up writing:

Will also update this thread when complete if possible

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.