diff --git a/.Rbuildignore b/.Rbuildignore index 054e0b7..bda3ed1 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -18,3 +18,5 @@ ^bin$ ^CRAN-SUBMISSION$ ^CRAN-PREPARATION\.md$ +^CRAN-FEEDBACK\.md$ +cran-fixes.patch diff --git a/.gitignore b/.gitignore index 5f2d382..39bc276 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ symbols.rds *.dll .Rhistory .Rproj.user +cran-fixes.patch +*.tar.gz +ribiosArg.Rcheck diff --git a/DESCRIPTION b/DESCRIPTION index 8ef1d5c..9809fe8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,10 +9,13 @@ Authors@R: role = c("aut", "cre", "ctb"), email = "jitao_david.zhang@roche.com", comment = c(ORCID="0000-0002-3085-0909")), - person("F.Hoffmann-La Roche AG", role="cph")) + person(given = "Balazs", + family = "Banfai", + role = "ctb")) Description: Provides functions to handle command-line arguments for R scripting. It enables building stand-alone R programs that accept and - parse command-line options in BIOS style. + parse command-line options in 'BIOS' style. + Zhang (2025) . Depends: R (>= 3.4.0), ribiosUtils diff --git a/NAMESPACE b/NAMESPACE index e5a4b2c..2b444af 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ export(argGet) export(argGetPos) +export(argIsInit) export(argParse) export(argPresent) export(existArg) diff --git a/NEWS b/NEWS index 149119b..a9ce9d5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,8 @@ +2026-02-12: v1.5-1 + + Add R-level guards in argPresent, argGet, and argGetPos to return safe defaults when the parser is uninitialized + + Export argIsInit() for users to check parser initialization status + + argParse returns silently instead of stopping when no script context is detected (fixes --run-donttest examples) + 2015-09-04: v1.1-18 + rarg_parse: fixed a bug caused by passing SEXP as a size_t object diff --git a/NEWS.md b/NEWS.md deleted file mode 100644 index 589bb47..0000000 --- a/NEWS.md +++ /dev/null @@ -1,101 +0,0 @@ -# ribiosArg 1.5.0 - -* Initial CRAN submission -* Package prepared for CRAN submission with comprehensive documentation -* Depends on ribiosUtils (on CRAN) and uses its C headers via LinkingTo - -# ribiosArg 1.1.18 (2015-09-04) - -* `rarg_parse`: fixed bug caused by passing SEXP as a size_t object - -# ribiosArg 1.1.17 (2015-06-23) - -* `argParse` returns invisible NULL in all situations -* `argGetPos` and `argGet` returns default value in debugging mode - -# ribiosArg 1.1.16 (2015-06-08) - -* `parseNumVec` bug fix: returns failVal when input str is NULL - -# ribiosArg 1.1.15 (2015-02-09) - -* `makeFactor`: add 'verbose=FALSE' option - -# ribiosArg 1.1.14 (2015-01-12) - -* `argParse`: works with -e and --args options of R and Rscript - -# ribiosArg 1.1.13 (2014-10-07) - -* `argParse`: works with latest Rscript - -# ribiosArg 1.1.12 (2014-08-15) - -* `argParse`: add 'strict=TRUE' option for extra parameter handling - -# ribiosArg 1.1.11 (2014-06-23) - -* argparse C code: now aware of format strings in die/usage - -# ribiosArg 1.1.10 (2014-05-09) - -* `parseStrings` and `parsePairs` return NULL for NULL input -* Export `parseFactor` and `makeFactor` - -# ribiosArg 1.1.7 (2014-03-11) - -* `rarg_get` checks initialization to avoid segmentation error - -# ribiosArg 1.1.6 (2013-10-16) - -* `argParse` prints command line on parse failure - -# ribiosArg 1.1.5 (2013-07-29) - -* Add `parseStrings` and `parsePairs` -* ribiosArg now depends on ribiosUtils - -# ribiosArg 1.1.4 (2013-07-14) - -* `argPresent` returns FALSE if arg_init not called -* `argGet` prints error and returns NULL if arg_init not called - -# ribiosArg 1.1.3 (2013-05-15) - -* Add 'default' parameter to `argGet` and `argGetPos` -* Move `scriptInit()` to ribiosUtils - -# ribiosArg 1.1.2 (2013-01-28) - -* `argParse`: optargs and reqargs can be NULL -* Replace log.h module with local mimics - -# ribiosArg 1.1.1 (2013-01-23) - -* Add `scriptInit` to prepare R for the script - -# ribiosArg 1.1.0 (2013-01-04) - -* Add BIOS-style command-line parser: `argParser`, `argGet`, `argGetPos`, `argPresent` - -# ribiosArg 1.0.5 (2012-01-24) - -* Add `RscriptName` function to get current script file name - -# ribiosArg 1.0.4 (2011-12-19) - -* `parseNumVec` accepts NULL as expLen for variable-length vectors - -# ribiosArg 1.0.3 (2011-12-15) - -* `getArg` parameter handling improvements -* Add tests folder for consistent behavior testing - -# ribiosArg 1.0.2 (2011-10-10) - -* `getArg` differentiates options and negative numbers -* `parseNumVec` removes leading and trailing quotes - -# ribiosArg 1.0.1 (2011-10-04) - -* `parseNumVec` suppresses warnings during string-to-number conversion diff --git a/R/argparse.R b/R/argparse.R index 1a7e8ac..8978670 100644 --- a/R/argparse.R +++ b/R/argparse.R @@ -1,5 +1,4 @@ #' Parser of command-line parameters in BIOS style -#' @aliases argIsInit #' @aliases argPresent #' #' @param optargs String describing optional arguments. Syntax: \code{[,paramcnt1] [,paramcnt2]\dots}. Example: \dQuote{verbose outfile,1} means the command line has the syntax \code{prog [-verbose] [outfile name]}. It can be an empty string to express \dQuote{no options}. The value for \code{paramcnt} is 0. @@ -37,7 +36,7 @@ #' @useDynLib ribiosArg, .registration=TRUE, .fixes="C_" #' #' @examples -#' \dontrun{ +#' \donttest{ #' argParse("verbose threshold,2", "infile outfile", #' usage="prog [-infile ]infile [-outfile ]outfile [-verbose] [-threshold MIN MAX]") #' argIsInit() @@ -70,8 +69,8 @@ argParse <- function(optargs, reqargs, usage=paste(scriptName(), "-h"), strict=T efind <- which(isE) allComm <- allComm[-(1:(efind+1))] } else { - stop("This should not happen: no parameters in the form of '-f' or '--f' is detected. Please contact the developer") - } + return(invisible(NULL)) + } comm <- allComm[!grepl("^--", allComm)] ## the following code was valid till R-3.0.x. ## if("--args" %in% allComm) { @@ -114,6 +113,10 @@ argParse <- function(optargs, reqargs, usage=paste(scriptName(), "-h"), strict=T } } +#' Check whether the argument parser has been initialized +#' +#' @return Logical, \code{TRUE} if \code{argParse} has been called, \code{FALSE} otherwise. +#' @export argIsInit <- function() .Call(C_rarg_isInit) #' Test whether the given option is present in the command line or not @@ -127,6 +130,7 @@ argPresent <- function(opt) { message("[DEBUGGIING] The script is running in an interactive session, e.g. debugging mode. FALSE is returned") return(FALSE) } + if(!isTRUE(argIsInit())) return(FALSE) .Call(C_rarg_present, opt) } @@ -147,14 +151,15 @@ argPresent <- function(opt) { #' @seealso \code{\link{argParse}}, \code{\link{argGet}}, and \code{\link{argPresent}} #' #' @examples -#' \dontrun{argGetPos("thresholds", ind=2)} +#' \donttest{argGetPos("thresholds", ind=2)} #' #' @export argGetPos <- function(opt, ind=1L, default=NULL, choices=NULL) { if(isDebugging()) { message("[DEBUGGIING] The script is running in an interactive session, e.g. debugging mode. Default value is returned") return(default) - } + } + if(!isTRUE(argIsInit())) return(default) if(argPresent(opt)) { res <- .Call(C_rarg_getPos, opt,as.integer(ind)) if(!is.null(choices) && !res %in% choices) @@ -182,7 +187,7 @@ argGetPos <- function(opt, ind=1L, default=NULL, choices=NULL) { #' @seealso \code{\link{argParse}}, \code{\link{argGetPos}}, and \code{\link{argPresent}} #' #' @examples -#' \dontrun{argGet("infile")} +#' \donttest{argGet("infile")} #' #' @export argGet <- function(opt, default=NULL, choices=NULL) { @@ -190,6 +195,7 @@ argGet <- function(opt, default=NULL, choices=NULL) { message("[DEBUGGIING] The script is running in an interactive session, e.g. debugging mode. Default value is returned") return(default) } + if(!isTRUE(argIsInit())) return(default) if(argPresent(opt)) { res <- .Call(C_rarg_get, opt) if(!is.null(choices) && !res %in% choices) { diff --git a/R/parseFuncs.R b/R/parseFuncs.R index 1d57dc6..d31bbe6 100644 --- a/R/parseFuncs.R +++ b/R/parseFuncs.R @@ -13,6 +13,7 @@ #' @param failVal If the parsing failed (for example length not correct, or non-numeric values were provided, this value will be returned #' @param sep Separator in the character string, default "," #' +#' @return A numeric vector of the parsed values, or \code{failVal} if parsing fails. #' @seealso \code{\link{argGet}} #' #' @export @@ -127,6 +128,7 @@ parsePairs <- function(str, collapse=",", sep="=", #' @param make.names Should names be converted to adhere to the rule of variable names in R #' @param verbose Logical vector #' +#' @return A factor with the specified levels. #' @export #' @examples #' makeFactor(c("A", "B", "C", "C", "A"), levels=LETTERS[3:1]) @@ -178,6 +180,7 @@ makeFactor <- function(groups, levels=NULL, make.names=TRUE, verbose=FALSE) { #' @param make.names Logical, should names be converted to adhere to the rule of variable names in R #' @param collapse Character used in \code{relevels} to collapse different levels #' +#' @return A factor parsed from the input string with the specified levels. #' @export #' @examples #' parseFactor("A,B,C,B,A", rlevels="A,B,C") @@ -194,14 +197,14 @@ makeFactor <- function(groups, levels=NULL, make.names=TRUE, verbose=FALSE) { #' groups <- factor(c("B", "C", "A", "D"), levels=c("D","C","A","B")) #' makeFactor(groups) #' -#' \dontrun{ +#' \donttest{ #' groups <- c("ATest", "Control", "Control", "ATest") #' levels <- c("Control", "ATest", "Unknown") #' makeFactor(groups, levels) #' #' groups <- c("ATest", "Control", "Control", "ATest", "BTest") #' levels <- c("Control", "ATest") -#' makeFactor(groups, levels) +#' try(makeFactor(groups, levels)) #' } #' parseFactor <- function(str, rlevels=NULL, make.names=TRUE, collapse=",") { ## CL=command line @@ -233,6 +236,7 @@ isDir <- function(str) file.info(str)$isdir #' @param recursive In cse of directory or compressed files, whether files should be found recursively #' @param ignore.case In case of directory or compressed files, whether case should be ignored #' +#' @return A character vector of file paths. #' @importFrom ribiosUtils extname #' @importFrom utils untar unzip #' @export diff --git a/R/ribiosArg.R b/R/ribiosArg.R index 46e553c..2f9eba7 100644 --- a/R/ribiosArg.R +++ b/R/ribiosArg.R @@ -1,7 +1,9 @@ -#' Command-line argument handling for R scripting -#' @docType package -#' @description Provides command-line argument handling for R scripting +#' @keywords internal +"_PACKAGE" + +#' ribiosIO +#' ribiosIO provides Command-line argument handling for R scripting #' @author Jitao David Zhang #' @useDynLib ribiosArg, .registration=TRUE, .fixes="C_" -#' @name ribiosArg-package +#' @name ribiosArg NULL diff --git a/R/scriptInit.R b/R/scriptInit.R index 73d72fd..e9332a7 100644 --- a/R/scriptInit.R +++ b/R/scriptInit.R @@ -1,17 +1,19 @@ #' Prepare the environment for a script -#' @aliases initScript -#' +#' #' This function is called at the beginning of an Rscript, in order to #' prepare the R environment to run in a script setting. #' -#' @return Only side effect is used -#' +#' @return No return value, called for side effects. +#' +#' @aliases initScript #' @export #' @examples -#' \dontrun{ +#' \donttest{ #' scriptInit() #' } scriptInit <- function() { + old <- options() + on.exit(options(old)) if(interactive()) { setDebug() } else { diff --git a/R/scriptName.R b/R/scriptName.R index 97f340f..a9e0342 100644 --- a/R/scriptName.R +++ b/R/scriptName.R @@ -15,7 +15,7 @@ #' #' @export #' @examples -#' \dontrun{scriptName()} +#' \donttest{scriptName()} #' scriptName <- function() { filename <- grep("--file=", commandArgs(), value=TRUE) @@ -49,7 +49,7 @@ scriptName <- function() { #' #' @export #' @examples -#' \dontrun{scriptPath()} +#' \donttest{scriptPath()} #' scriptPath <- function() { sname <- scriptName() diff --git a/R/scriptSkeleton.R b/R/scriptSkeleton.R index 99d9ab7..3222762 100644 --- a/R/scriptSkeleton.R +++ b/R/scriptSkeleton.R @@ -2,9 +2,11 @@ #' #' @param file Output file. By default the function writes to standard output. #' +#' @return Invisibly returns the character vector of skeleton lines. +#' Called for its side effect of writing to \code{file}. #' @export #' @examples -#' scriptSkeleton() +#' scriptSkeleton(file = file.path(tempdir(), "myscript.R")) scriptSkeleton <- function(file=stdout()) { sentences <- c("#!/usr/bin/env Rscript", diff --git a/man/argGet.Rd b/man/argGet.Rd index 2611e54..87ff877 100644 --- a/man/argGet.Rd +++ b/man/argGet.Rd @@ -23,7 +23,7 @@ Get the value of an named argument The parsing is performed at C-level. It is an abbreiviation of argGetPos(opt, ind=1, default=NULL, choices=NULL) } \examples{ -\dontrun{argGet("infile")} +\donttest{argGet("infile")} } \seealso{ diff --git a/man/argGetPos.Rd b/man/argGetPos.Rd index 1522e49..be323bb 100644 --- a/man/argGetPos.Rd +++ b/man/argGetPos.Rd @@ -25,7 +25,7 @@ Get the value of an named argument with the given position The parsing is performed at C-level. If the argument accepts only one value, users can also call argGet(opt, default=NULL, choices=NULL) } \examples{ -\dontrun{argGetPos("thresholds", ind=2)} +\donttest{argGetPos("thresholds", ind=2)} } \seealso{ diff --git a/man/argIsInit.Rd b/man/argIsInit.Rd new file mode 100644 index 0000000..ad57298 --- /dev/null +++ b/man/argIsInit.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/argparse.R +\name{argIsInit} +\alias{argIsInit} +\title{Check whether the argument parser has been initialized} +\usage{ +argIsInit() +} +\value{ +Logical, \code{TRUE} if \code{argParse} has been called, \code{FALSE} otherwise. +} +\description{ +Check whether the argument parser has been initialized +} diff --git a/man/argParse.Rd b/man/argParse.Rd index 78b3d0c..991b965 100644 --- a/man/argParse.Rd +++ b/man/argParse.Rd @@ -2,7 +2,6 @@ % Please edit documentation in R/argparse.R \name{argParse} \alias{argParse} -\alias{argIsInit} \alias{argPresent} \title{Parser of command-line parameters in BIOS style} \usage{ @@ -53,7 +52,7 @@ Test whether the given option is present in the command line or not \code{default} value will be returned. } \examples{ -\dontrun{ +\donttest{ argParse("verbose threshold,2", "infile outfile", usage="prog [-infile ]infile [-outfile ]outfile [-verbose] [-threshold MIN MAX]") argIsInit() diff --git a/man/makeFactor.Rd b/man/makeFactor.Rd index 5154fc4..4e246e1 100644 --- a/man/makeFactor.Rd +++ b/man/makeFactor.Rd @@ -15,6 +15,9 @@ makeFactor(groups, levels = NULL, make.names = TRUE, verbose = FALSE) \item{verbose}{Logical vector} } +\value{ +A factor with the specified levels. +} \description{ Make a factor } diff --git a/man/parseFactor.Rd b/man/parseFactor.Rd index f2c9a91..0bf8301 100644 --- a/man/parseFactor.Rd +++ b/man/parseFactor.Rd @@ -15,6 +15,9 @@ parseFactor(str, rlevels = NULL, make.names = TRUE, collapse = ",") \item{collapse}{Character used in \code{relevels} to collapse different levels} } +\value{ +A factor parsed from the input string with the specified levels. +} \description{ Parse a character string into factor } @@ -33,14 +36,14 @@ makeFactor(groups, levels) groups <- factor(c("B", "C", "A", "D"), levels=c("D","C","A","B")) makeFactor(groups) -\dontrun{ +\donttest{ groups <- c("ATest", "Control", "Control", "ATest") levels <- c("Control", "ATest", "Unknown") makeFactor(groups, levels) groups <- c("ATest", "Control", "Control", "ATest", "BTest") levels <- c("Control", "ATest") -makeFactor(groups, levels) +try(makeFactor(groups, levels)) } } diff --git a/man/parseFiles.Rd b/man/parseFiles.Rd index 088c4fd..85a32c5 100644 --- a/man/parseFiles.Rd +++ b/man/parseFiles.Rd @@ -23,6 +23,9 @@ parseFiles( \item{ignore.case}{In case of directory or compressed files, whether case should be ignored} } +\value{ +A character vector of file paths. +} \description{ Parse files from command line option, which can be (1) a string vector of files, (2) a file listing input files (e.g. pointer file), (3) a directory, or (4) a zip/tar/gz file (determined by suffix). In the later two cases, file patterns can be specified. } diff --git a/man/parseNumVec.Rd b/man/parseNumVec.Rd index c5db577..129ec9a 100644 --- a/man/parseNumVec.Rd +++ b/man/parseNumVec.Rd @@ -15,6 +15,9 @@ parseNumVec(str, expLen = 2, failVal = c(5, 5), sep = ",") \item{sep}{Separator in the character string, default ","} } +\value{ +A numeric vector of the parsed values, or \code{failVal} if parsing fails. +} \description{ Numeric vectors can be given as arguments in two ways: (1) separated by blanks or (2) separated by other common separators, such as comma diff --git a/man/ribiosArg-package.Rd b/man/ribiosArg-package.Rd index ec569a6..238c014 100644 --- a/man/ribiosArg-package.Rd +++ b/man/ribiosArg-package.Rd @@ -2,11 +2,10 @@ % Please edit documentation in R/ribiosArg.R \docType{package} \name{ribiosArg-package} -\alias{ribiosArg} \alias{ribiosArg-package} -\title{Command-line argument handling for R scripting} +\title{ribiosArg: Argument Handling for Command-Line, Stand-Alone R Scripts} \description{ -Provides command-line argument handling for R scripting +Provides functions to handle command-line arguments for R scripting. It enables building stand-alone R programs that accept and parse command-line options in 'BIOS' style. Zhang (2025) \url{https://github.com/bedapub/ribiosArg}. } \seealso{ Useful links: @@ -17,5 +16,7 @@ Useful links: } \author{ -Jitao David Zhang +\strong{Maintainer}: Jitao David Zhang \email{jitao_david.zhang@roche.com} (\href{https://orcid.org/0000-0002-3085-0909}{ORCID}) [contributor] + } +\keyword{internal} diff --git a/man/ribiosArg.Rd b/man/ribiosArg.Rd new file mode 100644 index 0000000..d7c1f2f --- /dev/null +++ b/man/ribiosArg.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ribiosArg.R +\name{ribiosArg} +\alias{ribiosArg} +\title{ribiosIO +ribiosIO provides Command-line argument handling for R scripting} +\description{ +ribiosIO +ribiosIO provides Command-line argument handling for R scripting +} +\author{ +Jitao David Zhang +} diff --git a/man/scriptInit.Rd b/man/scriptInit.Rd index 83cc315..7ff44cb 100644 --- a/man/scriptInit.Rd +++ b/man/scriptInit.Rd @@ -3,38 +3,19 @@ \name{scriptInit} \alias{scriptInit} \alias{initScript} -\alias{This} -\alias{function} -\alias{is} -\alias{called} -\alias{at} -\alias{the} -\alias{beginning} -\alias{of} -\alias{an} -\alias{Rscript,} -\alias{in} -\alias{order} -\alias{to} -\alias{prepare} -\alias{R} -\alias{environment} -\alias{run} -\alias{a} -\alias{script} -\alias{setting.} \title{Prepare the environment for a script} \usage{ scriptInit() } \value{ -Only side effect is used +No return value, called for side effects. } \description{ -Prepare the environment for a script +This function is called at the beginning of an Rscript, in order to +prepare the R environment to run in a script setting. } \examples{ -\dontrun{ +\donttest{ scriptInit() } } diff --git a/man/scriptName.Rd b/man/scriptName.Rd index e201352..51d7afe 100644 --- a/man/scriptName.Rd +++ b/man/scriptName.Rd @@ -20,7 +20,7 @@ When the R session was not initiated by a Rscript (i.e. there is no \code{--file Note that the function supports calling Rscript via \code{--file} or \code{-f} with \code{R}. This applies to cases where a Rscript, marked as executable, and is called from the command line. } \examples{ -\dontrun{scriptName()} +\donttest{scriptName()} } \seealso{ diff --git a/man/scriptPath.Rd b/man/scriptPath.Rd index 0631250..7303e39 100644 --- a/man/scriptPath.Rd +++ b/man/scriptPath.Rd @@ -20,7 +20,7 @@ When the R session was not initiated by a Rscript (i.e. there is no \code{--file Note that the function supports calling Rscript via \code{--file} or \code{-f} with \code{R}. This applies to cases where a Rscript, marked as executable, and is called from the command line. } \examples{ -\dontrun{scriptPath()} +\donttest{scriptPath()} } \seealso{ diff --git a/man/scriptSkeleton.Rd b/man/scriptSkeleton.Rd index 822a4a5..382457b 100644 --- a/man/scriptSkeleton.Rd +++ b/man/scriptSkeleton.Rd @@ -9,9 +9,13 @@ scriptSkeleton(file = stdout()) \arguments{ \item{file}{Output file. By default the function writes to standard output.} } +\value{ +Invisibly returns the character vector of skeleton lines. + Called for its side effect of writing to \code{file}. +} \description{ Generate a Rscript with its skeleton } \examples{ -scriptSkeleton() +scriptSkeleton(file = file.path(tempdir(), "myscript.R")) } diff --git a/tests/test_argPresent_uninit.R b/tests/test_argPresent_uninit.R new file mode 100644 index 0000000..b044a48 --- /dev/null +++ b/tests/test_argPresent_uninit.R @@ -0,0 +1,13 @@ +library(ribiosArg) + +# Ensure argPresent returns FALSE (not error) without argParse +res <- argPresent("nonexistent") +stopifnot(identical(res, FALSE)) + +# Ensure argGet returns default without argParse +res2 <- argGet("nonexistent", default = "mydefault") +stopifnot(identical(res2, "mydefault")) + +# argGetPos returns default +res3 <- argGetPos("nonexistent", ind = 1L, default = 42) +stopifnot(identical(res3, 42))