1

Using argparser in R, I'm getting an error when specifying the type of an argument in the call to add_argument but not passing an argument to the script at the MacOSX command line. For example, given this R script:

library(argparser)
p <- arg_parser(description = "A test parser")
p <- add_argument(p, "--alpha", type = "double", help = "alpha for p-value")
p <- add_argument(p, "--sig-digits", type = "integer", help="number of significant digits")
args <- parse_args(p)
print(str(args))

and invoking it at the command line:

Rscript argparser-test.R --alpha 0.1

I am returned the error:

Error in (function (object, class, nargs)  :
Invalid argument value: expecting integer but got: (NA).
Calls: parse_args -> mapply -> <Anonymous>

Interestingly, there is no error if you let --alpha take it's default:

Rscript argparser-test.R

Returns:

List of 5
 $           : logi FALSE
 $ help      : logi FALSE
 $ opts      : logi NA
 $ alpha     : logi NA
 $ sig_digits: logi NA
NULL

Notice the NA value here for sig_digits is type logical, not integer, as defined in the add_argument function.

Am I doing something wrong here? In the mean time, I suppose I will get around this by making the default --sig-digits = -1, then handling that as an exception, but I'd prefer not to.

Update: Actually, -1 throws the same error, which is very frustrating because I want to use a number for the exception that non-sensical. 9999 works, and is unlikely to be input by the user, but actually it's valid.

tef2128
  • 740
  • 1
  • 8
  • 19

1 Answers1

1

I experienced this error a month back or so. This is a problem with how optional arguments are parsed by the argparser package. Basically it does respect the order of optional arguments as it should in every situation, and sometimes it thus expects the wrong argument type.

I've opened an issue on the package bitbucket page. I highly suggest upvoting this and adding a comment to help adding to the attention of the issue.

In my issue I provided a possible solution to the problem which amounts to changing parse_args to the following definition (one could pull and recreate the package with this function at which point it would [should] work as expected)

parse_args <- function (parser, argv = commandArgs(trailingOnly = TRUE)) 
{
    stopifnot(is(parser, "arg.parser"))
    values <- list()
    argv <- preprocess_argv(argv, parser)
    arg.flags <- parser$args[parser$is.flag]
    x <- as.logical(parser$defaults[parser$is.flag])
    x[is.na(x)] <- FALSE
    names(x) <- sub("^-+", "", arg.flags)
    flag.idx <- match(arg.flags, argv)
    flag.idx <- flag.idx[!is.na(flag.idx)]
    if (length(flag.idx) > 0) {
        x[match(argv[flag.idx], arg.flags)] <- TRUE
        argv <- argv[-flag.idx]
    }
    values <- c(values, x)
    if (values$help) {
        print(parser)
        quit()
    }
    x <- parser$defaults[parser$is.opt.arg]
    arg.opt <- parser$args[parser$is.opt.arg]
    names(x) <- sub("^-+", "", arg.opt)
    i <- match("--opts", argv)
    if (!is.na(i)) {
        opts <- readRDS(argv[i + 1])
        opts <- opts[!names(opts) %in% c("opts", "help")]
        idx <- match(sanitize_arg_names(names(opts)), sanitize_arg_names(names(x)))
        if (any(is.na(idx))) {
            stop("Extra arguments supplied in OPTS file: (", 
                paste(setdiff(names(opts), names(x)), collapse = ", "), 
                ").")
        }
        x[idx] <- opts
    }
    arg.idx <- match(arg.opt, argv)
    arg.idx <- arg.idx[!is.na(arg.idx)]
    arg.opt.types <- parser$types[parser$is.opt.arg]
    arg.opt.nargs <- parser$nargs[parser$is.opt.arg]
    ###               ###
    ## Altered section ##
    ###               ###
    if (length(arg.idx) > 0) {
        # extract values following the optional argument label
        x[ind <- match(argv[arg.idx], arg.opt)] <- argv[arg.idx+1];
        # convert type of extraced values; x is now a list
        x[ind] <- mapply(convert_type,                                
                         object = x[ind], 
                         class = arg.opt.types[ind], 
                         nargs = arg.opt.nargs[ind], 
                         SIMPLIFY = FALSE);                                    
        # remove extracted arguments
        to.remove <- c(arg.idx, arg.idx+1);
        argv <- argv[-to.remove];
    }
    ###               ###
    ## Altered section ##
    ###               ###
    values <- c(values, x)
    x <- argv
    args.req <- parser$args[parser$is.req.arg]
    args.req.types <- parser$types[parser$is.req.arg]
    args.req.nargs <- parser$nargs[parser$is.req.arg]
    if (length(x) < length(args.req)) {
        print(parser)
        stop(sprintf("Missing required arguments: expecting %d values but got %d values: (%s).", 
            length(args.req), length(x), paste(x, collapse = ", ")))
    }
    else if (length(x) > length(args.req)) {
        print(parser)
        stop(sprintf("Extra arguments supplied: expecting %d values but got %d values: (%s).", 
            length(args.req), length(x), paste(x, collapse = ", ")))
    }
    else if (length(args.req) > 0) {
        names(x) <- args.req
        x <- mapply(convert_type, object = x, class = args.req.types, 
            nargs = args.req.nargs, SIMPLIFY = FALSE)
    }
    values <- c(values, x)
    names(values) <- sanitize_arg_names(names(values))
    values
}
Oliver
  • 8,169
  • 3
  • 15
  • 37
  • `Error in preprocess_argv(argv, parser) : could not find function "preprocess_argv"` – tef2128 Jul 28 '20 at 17:53
  • Yes, that makes sense. The function calls several functions within the `argparser` environment, and these may not be "attached" (eg: you can see them using `argparser:::preprocess_argv`. To use it without downloading and repackaging the function do: 1) Execute the function, to get errors as above. 2) For every error use search and replace for the function mentioned in the error and replace with with `argparser:::[name of function that gives error]`. At some point it hopefuly stops giving errors. I tested it by doing just that my alteration didn't break the function. – Oliver Jul 28 '20 at 17:53
  • 1
    bingo! all fixed, thanks so much for doing and sharing this work. – tef2128 Jul 28 '20 at 18:00
  • 1
    My pleasure. It does seem like the author of the package is not very interested in this package. I considered extending and pushing a `argparser2020` package to cran, but I've decided to give the author **djhshih** some time before doing so. – Oliver Jul 28 '20 at 18:03
  • Another thing, `argparser` parses multi-value arguments to a comma separated string, rather than a list as with `argparse`. Much less good, right? – tef2128 Jul 28 '20 at 18:54
  • Could you come with an example? I honestly haven't explored this avenue, but it sounds like an easy fix as well – Oliver Jul 28 '20 at 19:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/218774/discussion-between-oliver-and-tim-farkas). – Oliver Jul 28 '20 at 20:35