Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Encoding: UTF-8
Package: optparse
Type: Package
Title: Command Line Option Parser
Version: 1.8.0-5
Version: 1.8.0-6
Authors@R: c(person("Trevor L.", "Davis", role=c("aut", "cre"),
email="trevor.l.davis@gmail.com",
comment = c(ORCID = "0000-0001-6341-4639")),
Expand Down
11 changes: 11 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ New features

* `add_option()` (and `make_option()`) now support a `const` parameter (intended for new actions `"append_const"` and `"store_const"`).

* Several new classed errors are now thrown, mirroring Python's `optparse` exception hierarchy:

+ `optparse_option_error`: invalid option definition in `make_option()` or `add_option()`.
- `optparse_option_conflict_error`: duplicate flag in `OptionParser()` or `add_option()`.

+ `optparse_parse_error`: existing base class for all parse-time errors from `parse_args()`.
- `optparse_bad_option_error`: unrecognized, misused, or argument-requiring option.
* `optparse_ambiguous_option_error`: ambiguous abbreviated long flag.
- `optparse_bad_positional_arguments_error`: wrong number of positional arguments supplied.
- `optparse_missing_required_error`: a required option was not supplied.

* `add_option()` (and `make_option()`) now allow non-letter characters in short flags (e.g., `-1`) and at the beginning of long flags (e.g., `--1flag`), following Python's `optparse` convention. However long and short flags may not contain `=` or whitespace (or begin with hyphens).

* `add_option()` (and `make_option()`) now support a `required` argument.
Expand Down
24 changes: 18 additions & 6 deletions R/getopt.R
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ getopt <- function(
action %in%
c("append_const", "count", "store_const", "store_false", "store_true")
) {
stop(paste0('long flag "', this_flag, '" accepts no arguments'))
bad_option_error_stop(paste0(
'long flag "',
this_flag,
'" accepts no arguments'
))
} else if (action == "append") {
result[[dest_name]] <- c(result[[dest_name]], this_argument)
i <- i + 1L
Expand All @@ -92,7 +96,7 @@ getopt <- function(
i <- i + 1L
next
} else {
stop(paste0('"', optstring, '" is not a valid option, or does not support an argument'))
bad_option_error_stop(paste0('"', optstring, '" is not a valid option'))
}

long_name <- spec[rowmatch, COL_LONG_NAME]
Expand Down Expand Up @@ -134,7 +138,13 @@ getopt <- function(
}
}
if (action %in% c("store", "append")) {
stop(paste0('flag `', this_flag, '` requires an argument'))
flag_kind <- ifelse(is_long_flag(opt[i]), "long", "short")
bad_option_error_stop(paste0(
flag_kind,
' flag "',
this_flag,
'" requires an argument'
))
}
}
}
Expand Down Expand Up @@ -185,6 +195,8 @@ get_Rscript_filename <- function() {

command_args <- function() commandArgs()

is_interactive <- function() interactive()

littler_script_path <- function() Sys.getenv("LITTLER_SCRIPT_PATH", unset = NA_character_)

is_negative_number <- function(x) {
Expand Down Expand Up @@ -245,17 +257,17 @@ get_rowmatch <- function(spec, long = NULL, short = NULL) {
if (!is.null(long)) {
rowmatch <- grep(long, spec[, COL_LONG_NAME], fixed = TRUE)
if (length(rowmatch) == 0L) {
stop(paste0('long flag "', long, '" is invalid'))
bad_option_error_stop(paste0('long flag "', long, '" is invalid'))
} else if (length(rowmatch) > 1L) {
rowmatch <- which(long == spec[, COL_LONG_NAME])
if (length(rowmatch) == 0L) {
stop(paste0('long flag "', long, '" is ambiguous'))
ambiguous_option_error_stop(paste0('long flag "', long, '" is ambiguous'))
}
}
} else {
rowmatch <- grep(short, spec[, COL_SHORT_NAME], fixed = TRUE)
if (length(rowmatch) == 0L) {
stop(paste0('short flag "', short, '" is invalid'))
bad_option_error_stop(paste0('short flag "', short, '" is invalid'))
}
}
rowmatch
Expand Down
104 changes: 82 additions & 22 deletions R/optparse.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ setValidity("OptionParser", function(object) {
" (did you forget to set `add_help_option = FALSE` in `OptionParser()`?)"
)
}
return(msg)
option_conflict_error_stop(msg)
}
short_flags <- na_omit(vapply(object@options, \(o) o@short_flag, character(1L)))
if (anyDuplicated(short_flags)) {
Expand All @@ -81,7 +81,7 @@ setValidity("OptionParser", function(object) {
" (did you forget to set `add_help_option = FALSE` in `OptionParser()`?)"
)
}
return(msg)
option_conflict_error_stop(msg)
}
TRUE
})
Expand Down Expand Up @@ -205,6 +205,18 @@ OptionParser <- function(
))
}

option_error_stop <- function(msg, call = sys.call(-1)) {
stop(errorCondition(msg, class = "optparse_option_error", call = call))
}

option_conflict_error_stop <- function(msg, call = sys.call(-1)) {
stop(errorCondition(
msg,
class = c("optparse_option_conflict_error", "optparse_option_error"),
call = call
))
}

#' Functions to enable our OptionParser to recognize specific command line
#' options.
#'
Expand Down Expand Up @@ -232,6 +244,13 @@ OptionParser <- function(
#' @param callback_args `r ro_callback_args`
#' @return Both [make_option()] and [add_option()] return instances of
#' class `OptionParserOption`.
#' @section Errors:
#' The following classed errors may be thrown:
#'
#' - `optparse_option_error`: invalid option definition (bad flag string,
#' unrecognized action, etc.).
#' - `optparse_option_conflict_error`: duplicate flag detected when
#' adding an option to a parser.
#'
#' @seealso [parse_args()] [OptionParser()]
#' @references Python's `optparse` library, which inspires this package,
Expand Down Expand Up @@ -280,28 +299,28 @@ make_option <- function(
short_flag <- NA_character_
} else {
if (nchar(short_flag) > 2) {
stop(paste(
option_error_stop(paste(
"Short flag",
short_flag,
"must only be a '-' and a single non-dash character"
))
}
if (grepl("[[:space:]]", short_flag)) {
stop(paste("Short flag", short_flag, "must not contain whitespace"))
option_error_stop(paste("Short flag", short_flag, "must not contain whitespace"))
}
if (grepl("=", short_flag, fixed = TRUE)) {
stop(paste("Short flag", short_flag, "must not contain '='"))
option_error_stop(paste("Short flag", short_flag, "must not contain '='"))
}
}
long_flag <- opt_str[grepl("^--[^-]", opt_str)]
if (length(long_flag) == 0) {
stop("We require a long flag option")
option_error_stop("We require a long flag option")
}
if (grepl("=", long_flag, fixed = TRUE)) {
stop(paste("Long flag", long_flag, "must not contain '='"))
option_error_stop(paste("Long flag", long_flag, "must not contain '='"))
}
if (grepl("[[:space:]]", long_flag)) {
stop(paste("Long flag", long_flag, "must not contain whitespace"))
option_error_stop(paste("Long flag", long_flag, "must not contain whitespace"))
}

# type
Expand Down Expand Up @@ -564,14 +583,17 @@ as_string <- function(default) {
#' positional arguments. For backward compatibility, if and only if
#' `positional_arguments` is `FALSE`, returns a list containing
#' option values.
#' @section Acknowledgement:
#' A big thanks to Steve Lianoglou for a bug report and patch;
#' Juan Carlos \enc{Borrás}{Borras} for a bug report;
#' Jim Nikelski for a bug report and patch;
#' Ino de Brujin and Benjamin Tyner for a bug report;
#' Jonas Zimmermann for bug report; Miroslav Posta for bug reports;
#' Stefan Seemayer for bug report and patch;
#' Kirill \enc{Müller}{Muller} for patches; Steve Humburg for patch.
#' @section Errors:
#' The following classed errors may be thrown:
#'
#' - `optparse_parse_error`: base class for all parse-time errors.
#' - `optparse_bad_option_error`: unrecognized, misused, or
#' argument-requiring option.
#' - `optparse_ambiguous_option_error`: ambiguous abbreviated long
#' flag.
#' - `optparse_bad_positional_arguments_error`: wrong number of
#' positional arguments supplied.
#' - `optparse_missing_required_error`: a required option was not supplied.
#'
#' @seealso [OptionParser()] [print_help()]
#' @references Python's `optparse` library, which inspired this package,
Expand Down Expand Up @@ -634,13 +656,51 @@ quieter_error_handler <- function(e) {
quit('no', status = 1, runLast = FALSE)
}

bad_option_error_stop <- function(msg) {
stop(errorCondition(
msg,
class = c("optparse_bad_option_error", "optparse_parse_error"),
call = NULL
))
}

ambiguous_option_error_stop <- function(msg) {
stop(errorCondition(
msg,
class = c(
"optparse_ambiguous_option_error",
"optparse_bad_option_error",
"optparse_parse_error"
),
call = NULL
))
}

missing_required_error_stop <- function(msg) {
stop(errorCondition(
msg,
class = c("optparse_missing_required_error", "optparse_parse_error"),
call = NULL
))
}

bad_positional_arguments_error_stop <- function(msg) {
stop(errorCondition(
msg,
class = c("optparse_bad_positional_arguments_error", "optparse_parse_error"),
call = NULL
))
}

pa_stop <- function(object, e) {
extra_classes <- setdiff(class(e), c("simpleError", "error", "condition"))
classes <- unique(c(extra_classes, "optparse_parse_error"))
cnd <- errorCondition(
e$message,
call = "optparse::parse_args_helper()",
class = "optparse_parse_error"
class = classes
)
if (interactive()) {
if (is_interactive()) {
stop(cnd)
} else {
signalCondition(cnd)
Expand Down Expand Up @@ -682,7 +742,7 @@ parse_args_helper <- function(
if (any(grepl("^help$", names(options_list)))) {
if (options_list[["help"]] && print_help_and_exit) {
print_help(object)
if (interactive()) {
if (is_interactive()) {
stop("help requested")
} else {
quit(status = 0)
Expand All @@ -697,21 +757,21 @@ parse_args_helper <- function(
}
}
if (length(missing_required)) {
stop(paste(
missing_required_error_stop(paste(
"the following arguments are required:",
paste(missing_required, collapse = ", ")
))
}

if (length(arguments_positional) < min(positional_arguments)) {
stop(sprintf(
bad_positional_arguments_error_stop(sprintf(
"required at least %g positional arguments, got %g",
min(positional_arguments),
length(arguments_positional)
))
}
if (length(arguments_positional) > max(positional_arguments)) {
stop(sprintf(
bad_positional_arguments_error_stop(sprintf(
"required at most %g positional arguments, got %g",
max(positional_arguments),
length(arguments_positional)
Expand Down
15 changes: 15 additions & 0 deletions README.Rrst
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,18 @@ The function ``parse_args2`` wraps ``parse_args`` while setting ``positional_arg

parse_args2(parser, args = c("-vc", "25", "75", "22"))
.. ..

acknowledgements
----------------

A big thanks to:

* Steve Lianoglou for a bug report and patch
* Juan Carlos Borrás for a bug report
* Jim Nikelski for a bug report and patch
* Ino de Brujin and Benjamin Tyner for a bug report
* Jonas Zimmermann for a bug report
* Miroslav Posta for bug reports
* Stefan Seemayer for a bug report and patch
* Kirill Müller for patches
* Steve Humburg for a patch
13 changes: 13 additions & 0 deletions man/add_make_option.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 16 additions & 8 deletions man/parse_args.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions tests/testthat/_snaps/optparse.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,23 +110,23 @@
parse_args(parser, c("file.txt"))
Condition
Error:
! "file.txt" is not a valid option, or does not support an argument
! "file.txt" is not a valid option

---

Code
parse_args(parser, c("--verbose", "file.txt"))
Condition
Error:
! "file.txt" is not a valid option, or does not support an argument
! "file.txt" is not a valid option

# Use h option for non-help

Code
OptionParser(usage = "\\%prog [options] file", option_list = option_list_neg)
Condition
Error in `validObject()`:
! invalid class "OptionParser" object: duplicate short flag: -h (did you forget to set `add_help_option = FALSE` in `OptionParser()`?)
Error in `validityMethod()`:
! duplicate short flag: -h (did you forget to set `add_help_option = FALSE` in `OptionParser()`?)

# no-argument actions reject --flag=value syntax

Expand Down
Loading
Loading