Skip to content

Commit

Permalink
feat: 'parse_known_args()' support
Browse files Browse the repository at this point in the history
closes #34
  • Loading branch information
trevorld committed Sep 7, 2021
1 parent cce09ae commit bcf00b4
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 42 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: argparse
Type: Package
Title: Command Line Optional and Positional Argument Parser
Version: 2.0.4
Version: 2.1.0
Authors@R: c(person("Trevor L", "Davis", role=c("aut", "cre"), email="[email protected]"),
person("Allen", "Day", role="ctb", comment="Some documentation and examples ported from the getopt package."),
person("Python Software Foundation", role="ctb", comment="Some documentation from the optparse Python module."),
Expand Down
16 changes: 12 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
argparse 2.1.0
==============

* Parsers now support ``parse_known_args()`` (#34).
Suggestion of David Lacalle Castillo (@WaterKnight1998).

argparse 2.0.4
==============

Expand Down Expand Up @@ -69,12 +75,15 @@ argparse 1.0.4
* You can now pass in a character vector to the ``metavar`` argument of ``add_argument``.
Thanks Paul Newell for bug report and a patch.
* `add_argument` now throws a warning recommending using action "store_true" or "store_false"
if type set to "logical" and action set to "store". Suggestion of Martí Duran Ferrer
if type set to "logical" and action set to "store".
Suggestion of Martí Duran Ferrer
* You can now explicitly set a `NULL` default in `add_argument`.
Previously one could only implicitly set a `NULL` default by not setting any default at all.
Suggestion of Hyunsoo Kim.
* Fixes parsing bug when using a very large argument list. Thanks Taylor Pospisil for bug report.
* Parse error usage message now prints to standard error. User requested help message quit status is now zero.
* Fixes parsing bug when using a very large argument list.
Thanks Taylor Pospisil for bug report.
* Parse error usage message now prints to standard error.
User requested help message quit status is now zero.
Thanks to PlasmaBinturong for report/request.
* If Python script fails with error pass on error message to user.
Thanks to Martí Duran Ferrer for report/request.
Expand All @@ -95,7 +104,6 @@ argparse 1.0.0
but will instead throw an error. ``argparse`` will continue to ``quit(status=1)`` after printing a help message
for non-interactive Rscripts.


argparse 0.5.3
==============

Expand Down
64 changes: 36 additions & 28 deletions R/argparse.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@
#'
#' @references Python's \code{argparse} library, which this package is based on,
#' is described here: \url{https://docs.python.org/3/library/argparse.html}
#' @section Acknowledgement:
#' A big thanks to Martin Diehl for a bug report.
#'
#' @import jsonlite
#' @import R6
Expand Down Expand Up @@ -134,32 +132,14 @@ Parser <- R6Class("Parser", # nolint
paste(sprintf("'%s'", args), collapse = ", ")),
"print(json.dumps(args.__dict__, sort_keys=True))")
output <- private$python_code$run(new_code)
if (grepl("^usage:", output[1])) {
has_positional_arguments <- any(grepl("^positional arguments:", output))
has_optional_arguments <- any(grepl("^optional arguments:", output))
if (has_positional_arguments || has_optional_arguments) {
.print_message_and_exit(output, "help requested:")
} else {
.stop(output, "parse error:")
}
} else if (grepl("^Traceback", output[1])) {
.stop(output, "Error: python error")
} else if (any(grepl("^SyntaxError: Non-ASCII character", output))) {
message <- paste("Non-ASCII character detected.",
"If you wish to use Unicode arguments/options",
"please upgrade to Python 3.2+",
"Please see file INSTALL for more details.")
.stop(message, "non-ascii character error:")
} else if (any(grepl("^SyntaxError: positional argument follows keyword argument", output)) ||
grepl("^SyntaxError: non-keyword arg after keyword arg", output[2])) {
message <- "Positional argument following keyword argument."
.stop(message, "syntax error:")
} else if (grepl("^\\{", output)) {
args <- jsonlite::fromJSON(paste(output, collapse = ""))
return(args)
} else { # presumably version number request
.print_message_and_exit(output, "version requested:")
}
parse_args_output(output)
},
parse_known_args = function(args = commandArgs(TRUE)) {
new_code <- c(sprintf("args_remainder = %s.parse_known_args([%s])", private$name,
paste(sprintf("'%s'", args), collapse = ", ")),
"print(json.dumps((args_remainder[0].__dict__, args_remainder[1])))")
output <- private$python_code$run(new_code)
parse_args_output(output)
},
print_help = function() {
cat(private$python_code$run(sprintf("%s.print_help()", private$name)), sep = "\n")
Expand Down Expand Up @@ -207,6 +187,34 @@ Parser <- R6Class("Parser", # nolint
n_mutually_exclusive_groups = 0, n_groups = 0)
)

parse_args_output <- function(output) {
if (grepl("^usage:", output[1])) {
has_positional_arguments <- any(grepl("^positional arguments:", output))
has_optional_arguments <- any(grepl("^optional arguments:", output))
if (has_positional_arguments || has_optional_arguments) {
.print_message_and_exit(output, "help requested:")
} else {
.stop(output, "parse error:")
}
} else if (grepl("^Traceback", output[1])) {
.stop(output, "Error: python error")
} else if (any(grepl("^SyntaxError: Non-ASCII character", output))) {
message <- paste("Non-ASCII character detected.",
"If you wish to use Unicode arguments/options",
"please upgrade to Python 3.2+",
"Please see file INSTALL for more details.")
.stop(message, "non-ascii character error:")
} else if (any(grepl("^SyntaxError: positional argument follows keyword argument", output)) ||
grepl("^SyntaxError: non-keyword arg after keyword arg", output[2])) {
message <- "Positional argument following keyword argument."
.stop(message, "syntax error:")
} else if (grepl("^\\{|^\\[", output)) {
args <- jsonlite::fromJSON(paste(output, collapse = ""))
return(args)
} else { # presumably version number request
.print_message_and_exit(output, "version requested:")
}
}

# @param argument argument to be converted from R to Python
convert_argument <- function(argument) {
Expand Down
4 changes: 0 additions & 4 deletions cran-comments.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
## Notes

* The examples and unit tests are now skipped if an appropriate
version of Python is not found as with the 'r-devel-windows-x86_64-gcc10-UCRT'
CRAN flavor.

* As in previous uploads while in a non-interactive session (i.e. in an
Rscript) if ``parse_args()`` observes a help flag it will print a usage
message and then call ``quit()``. Additionally if a user specifically adds
Expand Down
5 changes: 0 additions & 5 deletions man/ArgumentParser.Rd

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

11 changes: 11 additions & 0 deletions tests/testthat/test-argparse.R
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ test_that("version flags works as expected", {
expect_equal(length(el), 0)
})

context("parse_known_args()")
test_that("parse_known_args() works as expected", {
skip_if_not(detects_python())
parser <- ArgumentParser()
parser$add_argument("-o", "--output_filename", default = "outfile.txt")
a_r <- parser$parse_known_args(c("-o", "foobar.txt", "-n", "4"))
expect_equal(a_r[[1]]$output_filename, "foobar.txt")
expect_equal(a_r[[2]], c("-n", "4"))

})

context("ArgumentParser")
test_that("ArgumentParser works as expected", {
skip_if_not(detects_python())
Expand Down

0 comments on commit bcf00b4

Please sign in to comment.