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)?
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"
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?
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
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
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