├── .github
├── .gitignore
└── workflows
│ └── R-CMD-check.yaml
├── .lintr
├── tests
├── run-all.R
└── testthat
│ └── test-getopt.R
├── .gitignore
├── man
├── figures
│ ├── logo.png
│ └── logo.svg
├── sort_list.Rd
├── get_Rscript_filename.Rd
└── getopt.Rd
├── _pkgdown.yml
├── NAMESPACE
├── Rakefile
├── .Rbuildignore
├── .travis.yml
├── cran-comments.md
├── CONTRIBUTING
├── raw-data
└── logo.R
├── DESCRIPTION
├── .appveyor.yml
├── exec
└── example.R
├── R
├── utils.R
└── getopt.R
├── NEWS.md
├── README.rst
└── COPYING
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/.lintr:
--------------------------------------------------------------------------------
1 | linters: with_defaults(line_length_linter(120))
2 |
--------------------------------------------------------------------------------
/tests/run-all.R:
--------------------------------------------------------------------------------
1 | library("testthat")
2 |
3 | test_check("getopt")
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | README.md
3 | README.html
4 | pkgdown/
5 | revdep/
6 |
--------------------------------------------------------------------------------
/man/figures/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trevorld/r-getopt/HEAD/man/figures/logo.png
--------------------------------------------------------------------------------
/_pkgdown.yml:
--------------------------------------------------------------------------------
1 | destination: "../../websites/R/getopt/"
2 | url: "https://trevorldavis.com/R/getopt"
3 | development:
4 | mode: auto
5 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export(get_Rscript_filename)
4 | export(getopt)
5 | export(sort_list)
6 | import(stats)
7 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | desc "Build files for packaging"
2 | task :default do
3 | sh 'rst2html README.rst README.html'
4 | sh 'pandoc -t markdown_strict -o README.md README.rst'
5 | sh 'Rscript -e "pkgdown::build_site()"'
6 | end
7 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | CONTRIBUTING
2 | Rakefile
3 | README.rst
4 | README.html
5 | ^.appveyor.yml$
6 | ^.lintr$
7 | ^.travis.yml$
8 | ^_pkgdown.yml$
9 | cran-comments.md
10 | cran-comments.rst
11 | revdep
12 | ^\.github$
13 | ^pkgdown$
14 | ^raw-data$
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: r
2 | cache: packages
3 | r:
4 | - release
5 | - devel
6 | os:
7 | - linux
8 | sudo: false
9 | latex: false
10 | warnings_are_errors: true
11 | cran: https://cran.rstudio.com
12 | notifications:
13 | email:
14 | on_success: change
15 | on_failure: change
16 | r_github_packages:
17 | - jimhester/lintr
18 | after_success:
19 | - Rscript -e 'covr::codecov()'
20 | - Rscript -e 'lintr::lint_package()'
21 |
--------------------------------------------------------------------------------
/man/sort_list.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/utils.R
3 | \name{sort_list}
4 | \alias{sort_list}
5 | \title{Recursively sorts a list}
6 | \usage{
7 | sort_list(unsorted_list)
8 | }
9 | \arguments{
10 | \item{unsorted_list}{A list.}
11 | }
12 | \value{
13 | A sorted list.
14 | }
15 | \description{
16 | \code{sort_list()} returns a recursively sorted list
17 | }
18 | \examples{
19 | l <- list(b = 2, a = 1)
20 | sort_list(l)
21 | }
22 |
--------------------------------------------------------------------------------
/man/get_Rscript_filename.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/utils.R
3 | \name{get_Rscript_filename}
4 | \alias{get_Rscript_filename}
5 | \title{Returns file name being interpreted by Rscript}
6 | \usage{
7 | get_Rscript_filename()
8 | }
9 | \value{
10 | A string with the filename of the calling script.
11 | If not found (i.e. you are in a interactive session) returns \code{NA_character_}.
12 | }
13 | \description{
14 | \code{get_Rscript_filename()} returns the file name that \code{Rscript} is interpreting.
15 | }
16 |
--------------------------------------------------------------------------------
/cran-comments.md:
--------------------------------------------------------------------------------
1 | ## Test environments
2 |
3 | * local (linux, R 4.3.1)
4 | * win-builder (windows, R devel)
5 | * Github Actions (linux, R devel)
6 | * Github Actions (linux, R release)
7 | * Github Actions (linux, R oldrel)
8 | * Github Actions (osx, R release)
9 | * Github Actions (windows, R release)
10 |
11 | ## ``R CMD check --as-cran`` results
12 |
13 | Status: OK
14 |
15 | ## revdepcheck results
16 |
17 | We checked 3 reverse dependencies (1 from CRAN + 2 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
18 |
19 | * We saw 0 new problems
20 | * We failed to check 0 packages
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING:
--------------------------------------------------------------------------------
1 | Pull requests are welcome but not guaranteed to be accepted (especially if they break reverse compatibility).
2 | It might be a good idea to submit an issue asking whether such a feature would be accepted before writing a patch.
3 |
4 | When submitting a patch please state that you are giving me an non-exclusive Unlimited license to your contribution
5 | so I have the flexibility of updating the package license in the future while you retain the right to use your code
6 | on other projects as you see fit as well as the right to fork the version of package that you contributed to.
7 |
8 | Additionally, although not required, adding a unit test illustrating a bug or testing the new feature would be much appreciated.
9 |
--------------------------------------------------------------------------------
/raw-data/logo.R:
--------------------------------------------------------------------------------
1 | library("bittermelon")
2 | library("grid")
3 | library("piecepackr")
4 |
5 | font_file <- system.file("fonts/spleen/spleen-8x16.hex.gz", package = "bittermelon")
6 | font <- read_hex(font_file)
7 | getopt <- as_bm_bitmap("getopt", font = font)
8 | hashbang <- as_bm_bitmap("#!", font = font)
9 |
10 | draw_logo <- function() {
11 | hex <- pp_shape("convex6")
12 | grid.newpage()
13 | grid.draw(hex$shape(gp = gpar(col = NA, fill = "#CD7F32")))
14 | plot(getopt, col = c("transparent", "black"),
15 | vp = viewport(y = 0.65, width=0.75, height = 0.21))
16 | plot(hashbang, col = c("transparent", "black"),
17 | vp = viewport(y = 0.33, width = 0.5, height = 0.4))
18 | grid.draw(hex$mat(mat_width = 0.03, gp = gpar(col = NA, fill = "black")))
19 | }
20 |
21 | w <- 3.0
22 | svg("man/figures/logo.svg", width = w, height = w, bg = "transparent")
23 | draw_logo()
24 | dev.off()
25 |
26 | png("man/figures/logo.png", width = w, height = w, units = "in",
27 | res = 72, bg = "transparent")
28 | draw_logo()
29 | dev.off()
30 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Encoding: UTF-8
2 | Package: getopt
3 | Type: Package
4 | Title: C-Like 'getopt' Behavior
5 | Version: 1.20.4
6 | Authors@R: c(person("Trevor L", "Davis", role=c("aut", "cre"),
7 | email="trevor.l.davis@gmail.com",
8 | comment = c(ORCID = "0000-0001-6341-4639")),
9 | person("Allen", "Day", role="aut", comment="Original package author"),
10 | person("Roman", "Zenka", role="ctb"))
11 | URL: https://github.com/trevorld/r-getopt
12 | Imports:
13 | stats
14 | BugReports: https://github.com/trevorld/r-getopt/issues
15 | Description: Package designed to be used with Rscript to write
16 | '#!' shebang scripts that accept short and long flags/options.
17 | Many users will prefer using instead the packages optparse or argparse
18 | which add extra features like automatically generated help option and usage,
19 | support for default values, positional argument support, etc.
20 | License: GPL (>= 2)
21 | Suggests:
22 | testthat
23 | Roxygen: list(markdown = TRUE)
24 | RoxygenNote: 7.2.3
25 | Config/testthat/edition: 3
26 |
--------------------------------------------------------------------------------
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | # DO NOT CHANGE the "init" and "install" sections below
2 |
3 | # Download script file from GitHub
4 | init:
5 | ps: |
6 | $ErrorActionPreference = "Stop"
7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1"
8 | Import-Module '..\appveyor-tool.ps1'
9 |
10 | install:
11 | ps: Bootstrap
12 |
13 | cache:
14 | - C:\RLibrary
15 |
16 | # Adapt as necessary starting from here
17 | environment:
18 | global:
19 | USE_RTOOLS: true
20 | matrix:
21 | - R_VERSION: devel
22 |
23 | - R_VERSION: release
24 |
25 | build_script:
26 | - travis-tool.sh install_deps
27 |
28 | test_script:
29 | - travis-tool.sh run_tests
30 |
31 | on_failure:
32 | - 7z a failure.zip *.Rcheck\*
33 | - appveyor PushArtifact failure.zip
34 |
35 | on_success:
36 | - Rscript -e "covr::codecov()"
37 |
38 | artifacts:
39 | - path: '*.Rcheck\**\*.log'
40 | name: Logs
41 |
42 | - path: '*.Rcheck\**\*.out'
43 | name: Logs
44 |
45 | - path: '*.Rcheck\**\*.fail'
46 | name: Logs
47 |
48 | - path: '*.Rcheck\**\*.Rout'
49 | name: Logs
50 |
51 | - path: '\*_*.tar.gz'
52 | name: Bits
53 |
54 | - path: '\*_*.zip'
55 | name: Bits
56 |
--------------------------------------------------------------------------------
/exec/example.R:
--------------------------------------------------------------------------------
1 | #!/path/to/Rscript
2 | library('getopt')
3 | library('stats')
4 | #get options, using the spec as defined by the enclosed list.
5 | #we read the options from the default: commandArgs(TRUE).
6 | spec = matrix(c(
7 | 'verbose', 'v', 2, "integer",
8 | 'help' , 'h', 0, "logical",
9 | 'count' , 'c', 1, "integer",
10 | 'mean' , 'm', 1, "double",
11 | 'sd' , 's', 1, "double"
12 | ), byrow=TRUE, ncol=4)
13 | opt = getopt(spec)
14 |
15 | # if help was asked for print a friendly message
16 | # and exit with a non-zero error code
17 | if ( !is.null(opt$help) ) {
18 | cat(getopt(spec, usage=TRUE))
19 | q(status=1)
20 | }
21 |
22 | #set some reasonable defaults for the options that are needed,
23 | #but were not specified.
24 | if ( is.null(opt$mean ) ) { opt$mean = 0 }
25 | if ( is.null(opt$sd ) ) { opt$sd = 1 }
26 | if ( is.null(opt$count ) ) { opt$count = 10 }
27 | if ( is.null(opt$verbose ) ) { opt$verbose = FALSE }
28 |
29 | #print some progress messages to stderr, if requested.
30 | if ( opt$verbose ) { write("writing...",stderr()) }
31 |
32 | #do some operation based on user input.
33 | cat(paste(rnorm(opt$count,mean=opt$mean,sd=opt$sd),collapse="\n"))
34 | cat("\n")
35 |
36 | #signal success and exit.
37 | q(status=0)
38 |
--------------------------------------------------------------------------------
/man/figures/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | branches: [main, master]
8 |
9 | name: R-CMD-check
10 |
11 | jobs:
12 | R-CMD-check:
13 | runs-on: ${{ matrix.config.os }}
14 |
15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }})
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | config:
21 | - {os: macOS-latest, r: 'release'}
22 | - {os: windows-latest, r: 'release'}
23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
24 | - {os: ubuntu-latest, r: 'release'}
25 | - {os: ubuntu-latest, r: 'oldrel-1'}
26 |
27 | env:
28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
29 | R_KEEP_PKG_SOURCE: yes
30 |
31 | steps:
32 | - uses: actions/checkout@v3
33 |
34 | - uses: r-lib/actions/setup-pandoc@v2
35 |
36 | - uses: r-lib/actions/setup-r@v2
37 | with:
38 | r-version: ${{ matrix.config.r }}
39 | http-user-agent: ${{ matrix.config.http-user-agent }}
40 | use-public-rspm: true
41 |
42 | - uses: r-lib/actions/setup-r-dependencies@v2
43 | with:
44 | extra-packages: rcmdcheck
45 |
46 | - uses: r-lib/actions/check-r-package@v2
47 |
48 | - name: Show testthat output
49 | if: always()
50 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true
51 | shell: bash
52 |
53 | - name: Upload check results
54 | if: failure()
55 | uses: actions/upload-artifact@main
56 | with:
57 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results
58 | path: check
59 |
--------------------------------------------------------------------------------
/R/utils.R:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2011-2013 Trevor L. Davis
2 | #
3 | # This file is free software: you may copy, redistribute and/or modify it
4 | # under the terms of the GNU General Public License as published by the
5 | # Free Software Foundation, either version 2 of the License, or (at your
6 | # option) any later version.
7 | #
8 | # This file is distributed in the hope that it will be useful, but
9 | # WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 | # General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 | #' Returns file name being interpreted by Rscript
17 | #'
18 | #' `get_Rscript_filename()` returns the file name that `Rscript` is interpreting.
19 | #' @return A string with the filename of the calling script.
20 | #' If not found (i.e. you are in a interactive session) returns `NA_character_`.
21 | #' @export
22 | get_Rscript_filename <- function() { # nolint
23 | prog <- sub("--file=", "", grep("--file=", commandArgs(), value = TRUE)[1])
24 | if (.Platform$OS.type == "windows") {
25 | prog <- gsub("\\\\", "\\\\\\\\", prog)
26 | }
27 | prog
28 | }
29 |
30 | #' Recursively sorts a list
31 | #'
32 | #' `sort_list()` returns a recursively sorted list
33 | #' @param unsorted_list A list.
34 | #' @return A sorted list.
35 | #' @examples
36 | #' l <- list(b = 2, a = 1)
37 | #' sort_list(l)
38 | #' @export
39 | sort_list <- function(unsorted_list) {
40 | for (ii in seq(along = unsorted_list)) {
41 | if (is.list(unsorted_list[[ii]])) {
42 | unsorted_list[[ii]] <- sort_list(unsorted_list[[ii]])
43 | }
44 | }
45 | unsorted_list[sort(names(unsorted_list))]
46 | }
47 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | getopt 1.20.4
2 | =============
3 | * Documentation tweaks eliminating a CRAN check NOTE.
4 | * `{covr}` is no longer a "suggested" package in the `DESCRIPTION` file.
5 |
6 | getopt 1.20.3
7 | =============
8 | * Now by default ``getopt()`` won't override a user specified ``opt`` argument if ``argv`` is in the global environment.
9 | Will continue to use ``argv`` as a default for ``opt`` if it is in the global environment and the user does not specify an ``opt`` argument (for ``littler`` compatibility).
10 |
11 | getopt 1.20.2
12 | =============
13 | * Now allows one to pass an empty string to a character option.
14 | Thanks Matthew Flickinger for bug report.
15 |
16 | getopt 1.20.1
17 | =============
18 | * Now explicitly imports the ``na.omit()`` method from the ``stats`` package.
19 | Thanks Derrick Oswald for bug report.
20 | * Improved parsing for negative numbers preceded by a space instead of a '=' sign.
21 | Thanks Roman Zenka for improved regular expression.
22 | * Slightly more informative error message if `storage.mode()` coercion results in an `NA`.
23 | Thanks Roman Zenka for suggestion.
24 |
25 | getopt 1.20.0
26 | =============
27 | * Type of "numeric" in spec automatically cast to "double".
28 | Previously users might have had an error passing negative numbers if they
29 | accidentally specified "numeric" instead of "double".
30 | * Project website moved to https://github.com/trevorld/getopt
31 | * Exports new function ``sort_list()``.
32 |
33 | getopt 1.19.1
34 | =============
35 | * If a passed in option matches multiple options in the getopt specification but matches one exactly
36 | then `getopt` now uses that value instead of throwing a "long flag is ambiguous" error.
37 |
38 | getopt 1.19.0
39 | =============
40 | * Exports new function ``get_Rscript_filename()`` that returns name of calling script,
41 | `{getopt}` now uses this function value as default for ``command`` argument.
42 | * Documentation improved and now highlights differences
43 | between `{getopt}` and `{optparse}` packages for new undecided users.
44 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | getopt
2 | ======
3 |
4 | .. image:: https://www.r-pkg.org/badges/version/getopt
5 | :target: https://cran.r-project.org/package=getopt
6 | :alt: CRAN Status Badge
7 |
8 | .. image:: https://github.com/trevorld/r-getopt/workflows/R-CMD-check/badge.svg
9 | :target: https://github.com/trevorld/r-getopt/actions
10 | :alt: R-CMD-check
11 |
12 | .. image:: https://codecov.io/github/trevorld/r-getopt/branch/master/graph/badge.svg
13 | :target: https://app.codecov.io/github/trevorld/r-getopt?branch=master
14 | :alt: Coverage Status
15 |
16 | .. image:: https://cranlogs.r-pkg.org/badges/getopt
17 | :target: https://cran.r-project.org/package=getopt
18 | :alt: RStudio CRAN mirror downloads
19 |
20 | .. image:: https://tinyverse.netlify.com/badge/getopt
21 | :target: https://cran.r-project.org/package=getopt
22 | :alt: Number of dependencies
23 |
24 | .. raw:: html
25 |
26 |
27 |
28 | ``getopt`` is an R package designed to be used with ``Rscript`` to write
29 | "#!"-shebang scripts that accept short and long flags/options. Many users will
30 | prefer using instead the package `optparse `_
31 | which adds extra features (automatically generated help option and usage,
32 | support for default values, basic positional argument support).
33 |
34 | To install the last version released on CRAN use the following command:
35 |
36 | .. code:: r
37 |
38 | install.packages("getopt")
39 |
40 | To install the development version use the following command:
41 |
42 | .. code:: r
43 |
44 | install.packages("remotes")
45 | remotes:install_github("trevorld/r-getopt")
46 |
47 | example
48 | -------
49 |
50 | An example Rscript using ``getopt``:
51 |
52 | .. code:: r
53 |
54 | #!/path/to/Rscript
55 | library('getopt')
56 | # get options, using the spec as defined by the enclosed list.
57 | # we read the options from the default: commandArgs(TRUE).
58 | spec = matrix(c(
59 | 'verbose', 'v', 2, "integer",
60 | 'help' , 'h', 0, "logical",
61 | 'count' , 'c', 1, "integer",
62 | 'mean' , 'm', 1, "double",
63 | 'sd' , 's', 1, "double"
64 | ), byrow=TRUE, ncol=4)
65 | opt = getopt(spec)
66 |
67 | # if help was asked for print a friendly message
68 | # and exit with a non-zero error code
69 | if ( !is.null(opt$help) ) {
70 | cat(getopt(spec, usage=TRUE))
71 | q(status=1)
72 | }
73 |
74 | # set some reasonable defaults for the options that are needed,
75 | # but were not specified.
76 | if ( is.null(opt$mean ) ) { opt$mean = 0 }
77 | if ( is.null(opt$sd ) ) { opt$sd = 1 }
78 | if ( is.null(opt$count ) ) { opt$count = 10 }
79 | if ( is.null(opt$verbose ) ) { opt$verbose = FALSE }
80 |
81 | # print some progress messages to stderr, if requested.
82 | if ( opt$verbose ) { write("writing...",stderr()) }
83 |
84 | # do some operation based on user input.
85 | cat(rnorm(opt$count,mean=opt$mean,sd=opt$sd),sep="\n")
86 |
87 | # signal success and exit.
88 | # q(status=0)
89 |
--------------------------------------------------------------------------------
/man/getopt.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/getopt.R
3 | \name{getopt}
4 | \alias{getopt}
5 | \alias{getopt-package}
6 | \title{C-like getopt behavior}
7 | \usage{
8 | getopt(
9 | spec = NULL,
10 | opt = NULL,
11 | command = get_Rscript_filename(),
12 | usage = FALSE,
13 | debug = FALSE
14 | )
15 | }
16 | \arguments{
17 | \item{spec}{The getopt specification, or spec of what options are considered
18 | valid. The specification must be either a 4-5 column matrix, or a
19 | character vector coercible into a 4 column matrix using
20 | \code{matrix(x,ncol=4,byrow=TRUE)} command. The matrix/vector
21 | contains:
22 |
23 | Column 1: the \emph{long flag} name. A multi-character string.
24 |
25 | Column 2: \emph{short flag} alias of Column 1. A single-character
26 | string.
27 |
28 | Column 3: \emph{Argument} mask of the \emph{flag}. An integer.
29 | Possible values: 0=no argument, 1=required argument, 2=optional argument.
30 |
31 | Column 4: Data type to which the \emph{flag}'s argument shall be cast using
32 | \code{\link[=storage.mode]{storage.mode()}}. A multi-character string. This only considered
33 | for same-row Column 3 values of 1,2. Possible values: logical,
34 | integer, double, complex, character.
35 | If numeric is encountered then it will be converted to double.
36 |
37 | Column 5 (optional): A brief description of the purpose of the option.
38 |
39 | The terms \emph{option}, \emph{flag}, \emph{long flag}, \emph{short flag},
40 | and \emph{argument} have very specific meanings in the context of this
41 | document. Read the \dQuote{Description} section for definitions.}
42 |
43 | \item{opt}{This defaults to the return value of \code{commandArgs(TRUE)} unless
44 | \code{argv} is in the global environment in which case it uses that instead
45 | (this is for compatibility with \code{littler}).
46 |
47 | If R was invoked directly via the \code{R} command, this corresponds to all
48 | arguments passed to R after the \code{--args} flag.
49 |
50 | If R was invoked via the \code{Rscript} command, this corresponds to all
51 | arguments after the name of the R script file.
52 |
53 | Read about \code{\link[=commandArgs]{commandArgs()}} and \link{Rscript} to learn more.}
54 |
55 | \item{command}{The string to use in the usage message as the name of the
56 | script. See argument \emph{usage}.}
57 |
58 | \item{usage}{If \code{TRUE}, argument \code{opt} will be ignored and a usage
59 | statement (character string) will be generated and returned from \code{spec}.}
60 |
61 | \item{debug}{This is used internally to debug the \code{getopt()} function itself.}
62 | }
63 | \description{
64 | \code{getopt} is primarily intended to be used with \code{Rscript}. It
65 | facilitates writing \verb{#!} shebang scripts that accept short and long
66 | flags/options. It can also be used from \code{R} directly, but is probably less
67 | useful in this context.
68 | }
69 | \details{
70 | \code{\link[=getopt]{getopt()}} returns a list data structure containing names of the
71 | flags that were present in the character vector passed in under
72 | the \code{opt} argument. Each value of the list is coerced to the
73 | data type specified according to the value of the \code{spec} argument. See
74 | below for details.
75 |
76 | Notes on naming convention:
77 | \enumerate{
78 | \item An \emph{option} is one of the shell-split input strings.
79 | \item A \emph{flag} is a type of \emph{option}. a \emph{flag} can be defined as
80 | having no \emph{argument} (defined below), a required \emph{argument}, or an
81 | optional \emph{argument}.
82 | \item An \emph{argument} is a type of \emph{option}, and is the value associated
83 | with a flag.
84 | \item A \emph{long flag} is a type of \emph{flag}, and begins with the string
85 | \verb{--}. If the \emph{long flag} has an associated \emph{argument}, it may be
86 | delimited from the \emph{long flag} by either a trailing \code{=}, or may be
87 | the subsequent \emph{option}.
88 | \item A \emph{short flag} is a type of \emph{flag}, and begins with the string
89 | \code{-}. If a \emph{short flag} has an associated \emph{argument}, it is the
90 | subsequent \emph{option}. \emph{short flags} may be bundled together,
91 | sharing a single leading \code{-}, but only the final \emph{short flag} is able
92 | to have a corresponding \emph{argument}.
93 | }
94 |
95 | Many users wonder whether they should use the \code{getopt} package, \code{optparse} package,
96 | or \code{argparse} package.
97 | Here is some of the major differences:
98 |
99 | Features available in \code{getopt} unavailable in \code{optparse}
100 | \enumerate{
101 | \item As well as allowing one to specify options that take either
102 | no argument or a required argument like \code{optparse},
103 | \code{getopt} also allows one to specify option with an optional argument.
104 | }
105 |
106 | Some features implemented in \code{optparse} package unavailable in \code{getopt}
107 | \enumerate{
108 | \item Limited support for capturing positional arguments after the optional arguments
109 | when \code{positional_arguments} set to \code{TRUE} in \code{optparse::parse_args()}
110 | \item Automatic generation of an help option and printing of help text when encounters an \code{-h}
111 | \item Option to specify default arguments for options as well the
112 | variable name to store option values
113 | }
114 |
115 | There is also new package \code{argparse} introduced in 2012 which contains
116 | all the features of both getopt and optparse but which has a dependency on
117 | Python 2.7 or 3.2+.
118 |
119 | Some Features unlikely to be implemented in \code{getopt}:
120 | \enumerate{
121 | \item Support for multiple, identical flags, e.g. for \verb{-m 3 -v 5 -v}, the
122 | trailing \code{-v} overrides the preceding \verb{-v 5}, result is \code{v=TRUE} (or equivalent
123 | typecast).
124 | \item Support for multi-valued flags, e.g. \verb{--libpath=/usr/local/lib --libpath=/tmp/foo}.
125 | \item Support for lists, e.g. \verb{--define os=linux --define os=redhat} would
126 | set \code{result$os$linux=TRUE} and \code{result$os$redhat=TRUE}.
127 | \item Support for incremental, argument-less flags, e.g. \verb{/path/to/script -vvv} should set \code{v=3}.
128 | \item Support partial-but-unique string match on options, e.g. \code{--verb} and
129 | \code{--verbose} both match long flag \code{--verbose}.
130 | \item No support for mixing in positional arguments or extra arguments that
131 | don't match any options. For example, you can't do \verb{my.R --arg1 1 foo bar baz} and recover \code{foo}, \code{bar}, \code{baz} as a list. Likewise for \verb{my.R foo --arg1 1 bar baz}.
132 | }
133 | }
134 | \examples{
135 | #!/path/to/Rscript
136 | library('getopt')
137 | # get options, using the spec as defined by the enclosed list.
138 | # we read the options from the default: commandArgs(TRUE).
139 | spec = matrix(c(
140 | 'verbose', 'v', 2, "integer",
141 | 'help' , 'h', 0, "logical",
142 | 'count' , 'c', 1, "integer",
143 | 'mean' , 'm', 1, "double",
144 | 'sd' , 's', 1, "double"
145 | ), byrow=TRUE, ncol=4)
146 | opt = getopt(spec)
147 |
148 | # if help was asked for print a friendly message
149 | # and exit with a non-zero error code
150 | if (!is.null(opt$help)) {
151 | cat(getopt(spec, usage = TRUE))
152 | q(status = 1)
153 | }
154 |
155 | # set some reasonable defaults for the options that are needed,
156 | # but were not specified.
157 | if (is.null(opt$mean)) opt$mean <- 0
158 | if (is.null(opt$sd)) opt$sd <- 1
159 | if (is.null(opt$count)) opt$count <- 10
160 | if (is.null(opt$verbose)) opt$verbose <- FALSE
161 |
162 | # print some progress messages to stderr, if requested.
163 | if (opt$verbose) write("writing...", stderr())
164 |
165 | # do some operation based on user input.
166 | cat(rnorm(opt$count, mean = opt$mean, sd = opt$sd), sep = "\n")
167 |
168 | # signal success and exit.
169 | # q(status=0)
170 | }
171 | \author{
172 | Allen Day
173 | }
174 | \keyword{data}
175 |
--------------------------------------------------------------------------------
/tests/testthat/test-getopt.R:
--------------------------------------------------------------------------------
1 | test_that("getopt works as expected", {
2 | spec <- matrix(c(
3 | "verbose", "v", 2, "integer",
4 | "help", "h", 0, "logical",
5 | "dummy1", "d", 0, "logical",
6 | "dummy2", "e", 2, "logical",
7 | "count", "c", 1, "integer",
8 | "mean", "m", 1, "double",
9 | "sd", "s", 1, "double",
10 | "output", "O", 1, "character"
11 | ), ncol = 4, byrow = TRUE)
12 | expect_equal(sort_list(getopt(spec, c("-c", "-1", "-m", "-1.2"))),
13 | sort_list(list(ARGS = character(0), count = -1, mean = -1.2)))
14 | expect_equal(sort_list(getopt(spec, c("-v", "-m", "3"))),
15 | sort_list(list(ARGS = character(0), verbose = 1, mean = 3)))
16 | expect_equal(sort_list(getopt(spec, c("-m", "3", "-v"))),
17 | sort_list(list(ARGS = character(0), mean = 3, verbose = 1)))
18 | expect_equal(sort_list(getopt(spec, c("-m", "3", "-v", "2", "-v"))),
19 | sort_list(list(ARGS = character(0), mean = 3, verbose = 1)))
20 | expect_equal(sort_list(getopt(spec, c("-O", "-", "-m", "3"))),
21 | sort_list(list(ARGS = character(0), output = "-", mean = 3)))
22 | expect_equal(sort_list(getopt(spec, c("-O", "-", "-m", "3"))),
23 | sort_list(getopt(spec, c("-m", "3", "-O", "-"))))
24 | expect_equal(sort_list(getopt(spec, c("-de"))),
25 | sort_list(getopt(spec, c("-ed"))))
26 | expect_equal(sort_list(getopt(spec, c("-de"))),
27 | sort_list(list(ARGS = character(0), dummy1 = TRUE, dummy2 = TRUE)))
28 | expect_equal(sort_list(getopt(spec, c("-de", "1"))),
29 | sort_list(list(ARGS = character(0), dummy1 = TRUE, dummy2 = NA)))
30 | expect_equal(sort_list(getopt(spec, c("--verbose"))),
31 | sort_list(list(ARGS = character(0), verbose = 1)))
32 | expect_equal(sort_list(getopt(spec, c("--verbose", "--help"))),
33 | sort_list(list(ARGS = character(0), verbose = 1, help = TRUE)))
34 | expect_equal(sort_list(getopt(spec, c("--verbose", "--mean", "5"))),
35 | sort_list(list(ARGS = character(0), verbose = 1, mean = 5)))
36 | expect_equal(sort_list(getopt(spec, c("--mean=5"))),
37 | sort_list(list(ARGS = character(0), mean = 5)))
38 | expect_equal(sort_list(getopt(spec, c("--verbose", "--mean=5", "--sd", "5"))),
39 | sort_list(list(ARGS = character(0), verbose = 1, mean = 5, sd = 5)))
40 | expect_equal(sort_list(getopt(spec, c("--verbose", "--mean=5", "--sd", "5"))),
41 | sort_list(getopt(spec, c("--mean=5", "--sd", "5", "--verbose"))))
42 |
43 | spec <- c(
44 | "date", "d", 1, "character",
45 | "help", "h", 0, "logical",
46 | "getdata", "g", 0, "logical",
47 | "market", "m", 1, "character",
48 | "threshold", "t", 1, "double"
49 | )
50 | spec2 <- matrix(spec, ncol = 4, byrow = TRUE)
51 | # should give warning is spec is not matrix
52 | expect_warning(getopt(spec, c("--date", "20080421", "--market", "YM", "--getdata")))
53 | expect_equal(sort_list(getopt(spec2, c("--date", "20080421", "--market", "YM", "--getdata"))),
54 | sort_list(list(ARGS = character(0), date = "20080421", market = "YM", getdata = TRUE)))
55 | expect_equal(sort_list(getopt(spec2, c("--date", "20080421", "--market", "YM", "--getdata"))),
56 | sort_list(getopt(spec2, c("--date", "20080421", "--getdata", "--market", "YM"))))
57 | expect_output(getopt(spec2, c("--date", "20080421", "--getdata", "--market", "YM"), debug = TRUE),
58 | "processing ")
59 | expect_output(print(getopt(spec2, c("--date", "20080421", "--getdata", "--market", "YM"), usage = TRUE)),
60 | "Usage: ")
61 | })
62 | test_that("numeric is cast to double", {
63 | # Feature reported upstream (optparse) by Miroslav Posta
64 | spec <- matrix(c("count", "c", 1, "integer"), ncol = 4, byrow = TRUE)
65 | opt <- getopt(spec, c("-c", "-55"))
66 | expect_equal(typeof(opt$count), "integer")
67 |
68 |
69 | spec <- matrix(c("count", "c", 1, "numeric"), ncol = 4, byrow = TRUE)
70 | opt <- getopt(spec, c("-c", "-55.0"))
71 | expect_equal(typeof(opt$count), "double")
72 | })
73 | test_that("empty strings are handled correctly for mandatory character arguments", {
74 | spec <- matrix(c("string", "s", 1, "character",
75 | "number", "n", 1, "numeric"), ncol = 4, byrow = TRUE)
76 | expect_equal(getopt(spec, c("--string=foo"))$string, "foo")
77 | expect_equal(getopt(spec, c("--string="))$string, "")
78 | expect_warning(getopt(spec, c("--number=")))
79 |
80 | })
81 |
82 | test_that("negative numbers are handled correctly", {
83 | # Issue if negative number preceded by space instead of '=' reported by Roman Zenka
84 | spec <- matrix(c("count", "c", 1, "integer"), ncol = 4, byrow = TRUE)
85 | expect_equal(getopt(spec, c("-c", "5"))$count, 5)
86 | spec <- matrix(c("count", "c", 1, "integer"), ncol = 4, byrow = TRUE)
87 | expect_equal(getopt(spec, c("-c", "-5"))$count, -5)
88 | spec <- matrix(c("count", "c", 1, "integer"), ncol = 4, byrow = TRUE)
89 | expect_equal(getopt(spec, c("--count=-1E5"))$count, -1E5)
90 | spec <- matrix(c("count", "c", 1, "double"), ncol = 4, byrow = TRUE)
91 | expect_equal(getopt(spec, c("-c", "-5"))$count, -5)
92 | spec <- matrix(c("count", "c", 1, "double"), ncol = 4, byrow = TRUE)
93 | expect_equal(getopt(spec, c("-c", "-1e5"))$count, -1e5)
94 | spec <- matrix(c("count", "c", 1, "double"), ncol = 4, byrow = TRUE)
95 | expect_equal(getopt(spec, c("--count=-1.2e5"))$count, -1.2e5)
96 | spec <- matrix(c("count", "c", 1, "double"), ncol = 4, byrow = TRUE)
97 | expect_equal(getopt(spec, c("--count", "-1e5"))$count, -1e5)
98 | })
99 |
100 | test_that("more helpful warnings upon incorrect input", {
101 | # Give more pointed warning upon wildly incorrect input
102 | spec <- matrix(c("count", "c", 1, "integer"), ncol = 4, byrow = TRUE)
103 | expect_warning(getopt(spec, c("-c", "hello"))$count, paste("integer expected, got", dQuote("hello")))
104 |
105 | spec <- NULL
106 | expect_error(getopt(spec, ""), 'argument "spec" must be non-null.')
107 |
108 | spec <- c("foo", "f", 0)
109 | expect_error(getopt(spec, ""), "or a character vector with length divisible by 4, rtfm")
110 |
111 | spec <- matrix(c("foo", "f", 0, "integer"), ncol = 2)
112 | expect_error(getopt(spec, ""), '"spec" should have at least 4 columns.')
113 |
114 | spec <- matrix(c("foo", "f", 0, "integer", "bar", "b", 0, "integer"), ncol = 8)
115 | expect_error(getopt(spec, ""), '"spec" should have no more than 6 columns.')
116 | })
117 |
118 | test_that("don't throw error if multiple matches match one argument fully", {
119 | # test if partial name matches fully,
120 | # still throw error if multiple matches and doesn't match both fully
121 | # feature request from Jonas Zimmermann
122 | spec <- matrix(c(
123 | "foo", "f", 0, "logical",
124 | "foobar", "b", 0, "logical",
125 | "biz", "z", 0, "logical"
126 | ), ncol = 4, byrow = TRUE)
127 | expect_error(getopt(spec, c("--fo")))
128 | expect_equal(getopt(spec, c("--foo")), sort_list(list(ARGS = character(0), foo = TRUE)))
129 | })
130 |
131 | test_that("sort_list works as expected", {
132 | expect_equal(sort_list(list(a = 3, b = 2)), sort_list(list(b = 2, a = 3)))
133 | expect_false(identical(sort_list(list(b = 3, a = 2)), list(b = 3, a = 2)))
134 | expect_false(identical(sort_list(list(b = list(b = 3, c = 2), a = 2)),
135 | list(b = list(c = 2, b = 3), a = 2)))
136 | })
137 |
138 | test_that("Use h flag for non help", {
139 | spec <- matrix(c("foo", "h", 0, "logical"), ncol = 4, byrow = TRUE)
140 | expect_equal(getopt(spec, c("-h")), sort_list(list(ARGS = character(0), foo = TRUE)))
141 |
142 | spec <- matrix(c("foo", "h", 0, "logical",
143 | "help", "h", 0, "logical"), ncol = 4, byrow = TRUE)
144 | expect_error(getopt(spec, c("-h")), "redundant short names for flags")
145 |
146 | spec <- matrix(c("foo", "f", 0, "logical",
147 | "foo", "h", 0, "logical"), ncol = 4, byrow = TRUE)
148 | expect_error(getopt(spec, c("-h")), "redundant long names for flags")
149 | })
150 |
151 | test_that("Optional usage strings work as expected", {
152 | spec <- matrix(c(
153 | "foo", "f", 0, "logical", "foo usage",
154 | "foobar", "b", 1, "character", "foobar usage",
155 | "biz", "z", 2, "logical", "biz usage",
156 | "number", "n", 1, "numeric", "number usage",
157 | "help", "h", 0, "logical", "help"
158 | ), ncol = 5, byrow = TRUE)
159 | expect_output(cat(getopt(spec, usage = TRUE)), "foobar usage")
160 | })
161 |
162 | test_that("tests to get coverage up", {
163 | spec <- matrix(c(
164 | "foo", "f", 0, "logical", "foo usage",
165 | "foobar", "b", 1, "character", "foobar usage",
166 | "biz", "z", 2, "logical", "biz usage",
167 | "number", "n", 1, "numeric", "number usage",
168 | "help", "h", 0, "logical", "help"
169 | ), ncol = 5, byrow = TRUE)
170 | expect_error(getopt(spec, "--whodunit"), 'long flag "whodunit" is invalid')
171 |
172 | expect_error(getopt(spec, "--foo=4"), 'long flag "foo" accepts no arguments')
173 |
174 | # expect_error(getopt(spec, c("--foo", "4")), 'long flag "foo" accepts no arguments') # nolint
175 |
176 | expect_equal(getopt(spec, "--biz", "4"), sort_list(list(ARGS = character(0), biz = TRUE)))
177 |
178 | expect_warning(getopt(spec, c("-n", "bar")), paste("double expected, got", dQuote("bar")))
179 |
180 | expect_warning(getopt(spec, c("--number=bar")), paste("double expected, got", dQuote("bar")))
181 |
182 | expect_error(getopt(spec, "-n"), 'flag "n" requires an argument')
183 |
184 | expect_error(getopt(spec, "-p"), 'short flag "p" is invalid')
185 |
186 | expect_error(getopt(spec, "-nh"), 'short flag "n" requires an argument, but has none')
187 | expect_error(getopt(spec, "-fn"), 'flag "n" requires an argument')
188 |
189 | expect_error(getopt(spec, c("--number", 3, 4)), '"4" is not a valid option, or does not support an argument')
190 | expect_error(getopt(spec, c("-b")), '"b" requires an argument')
191 | expect_error(getopt(spec, c("--foobar")), 'flag "foobar" requires an argument')
192 | expect_error(getopt(spec, c("--foobar", "--help")), 'flag "foobar" requires an argument')
193 |
194 | expect_output(getopt(spec, c("-n", "2"), debug = TRUE), "short option: -n")
195 | expect_output(getopt(spec, c("-b", "-"), debug = TRUE), "lone dash")
196 | expect_warning(getopt(spec, c("-n", "-")), paste("double expected, got", dQuote("-")))
197 | })
198 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/R/getopt.R:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2008-2010 Allen Day
2 | # Copyright (c) 2011-2018 Trevor L. Davis
3 | #
4 | # This file is free software: you may copy, redistribute and/or modify it
5 | # under the terms of the GNU General Public License as published by the
6 | # Free Software Foundation, either version 2 of the License, or (at your
7 | # option) any later version.
8 | #
9 | # This file is distributed in the hope that it will be useful, but
10 | # WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | #' C-like getopt behavior
18 | #'
19 | #' `getopt` is primarily intended to be used with `Rscript`. It
20 | #' facilitates writing `#!` shebang scripts that accept short and long
21 | #' flags/options. It can also be used from `R` directly, but is probably less
22 | #' useful in this context.
23 | #'
24 | #' [getopt()] returns a list data structure containing names of the
25 | #' flags that were present in the character vector passed in under
26 | #' the `opt` argument. Each value of the list is coerced to the
27 | #' data type specified according to the value of the `spec` argument. See
28 | #' below for details.
29 | #'
30 | #' Notes on naming convention:
31 | #'
32 | #' 1. An \emph{option} is one of the shell-split input strings.
33 | #'
34 | #' 2. A \emph{flag} is a type of \emph{option}. a \emph{flag} can be defined as
35 | #' having no \emph{argument} (defined below), a required \emph{argument}, or an
36 | #' optional \emph{argument}.
37 | #'
38 | #' 3. An \emph{argument} is a type of \emph{option}, and is the value associated
39 | #' with a flag.
40 | #'
41 | #' 4. A \emph{long flag} is a type of \emph{flag}, and begins with the string
42 | #' `--`. If the \emph{long flag} has an associated \emph{argument}, it may be
43 | #' delimited from the \emph{long flag} by either a trailing `=`, or may be
44 | #' the subsequent \emph{option}.
45 | #'
46 | #' 5. A \emph{short flag} is a type of \emph{flag}, and begins with the string
47 | #' `-`. If a \emph{short flag} has an associated \emph{argument}, it is the
48 | #' subsequent \emph{option}. \emph{short flags} may be bundled together,
49 | #' sharing a single leading `-`, but only the final \emph{short flag} is able
50 | #' to have a corresponding \emph{argument}.
51 | #'
52 | #' Many users wonder whether they should use the `getopt` package, `optparse` package,
53 | #' or `argparse` package.
54 | #' Here is some of the major differences:
55 | #'
56 | #' Features available in `getopt` unavailable in `optparse`
57 | #'
58 | #' 1. As well as allowing one to specify options that take either
59 | #' no argument or a required argument like `optparse`,
60 | #' `getopt` also allows one to specify option with an optional argument.
61 | #'
62 | #' Some features implemented in `optparse` package unavailable in `getopt`
63 | #'
64 | #' 1. Limited support for capturing positional arguments after the optional arguments
65 | #' when `positional_arguments` set to `TRUE` in `optparse::parse_args()`
66 | #'
67 | #' 2. Automatic generation of an help option and printing of help text when encounters an `-h`
68 | #'
69 | #' 3. Option to specify default arguments for options as well the
70 | #' variable name to store option values
71 | #'
72 | #' There is also new package `argparse` introduced in 2012 which contains
73 | #' all the features of both getopt and optparse but which has a dependency on
74 | #' Python 2.7 or 3.2+.
75 | #'
76 | #' Some Features unlikely to be implemented in `getopt`:
77 | #'
78 | #' 1. Support for multiple, identical flags, e.g. for `-m 3 -v 5 -v`, the
79 | #' trailing `-v` overrides the preceding `-v 5`, result is `v=TRUE` (or equivalent
80 | #' typecast).
81 | #'
82 | #' 2. Support for multi-valued flags, e.g. `--libpath=/usr/local/lib
83 | #' --libpath=/tmp/foo`.
84 | #'
85 | #' 3. Support for lists, e.g. `--define os=linux --define os=redhat` would
86 | #' set `result$os$linux=TRUE` and `result$os$redhat=TRUE`.
87 | #'
88 | #' 4. Support for incremental, argument-less flags, e.g. `/path/to/script
89 | #' -vvv` should set `v=3`.
90 | #'
91 | #' 5. Support partial-but-unique string match on options, e.g. `--verb` and
92 | #' `--verbose` both match long flag `--verbose`.
93 | #'
94 | #' 6. No support for mixing in positional arguments or extra arguments that
95 | #' don't match any options. For example, you can't do `my.R --arg1 1 foo bar
96 | #' baz` and recover `foo`, `bar`, `baz` as a list. Likewise for `my.R foo
97 | #' --arg1 1 bar baz`.
98 | #'
99 | #' @aliases getopt getopt-package
100 | #' @param spec The getopt specification, or spec of what options are considered
101 | #' valid. The specification must be either a 4-5 column matrix, or a
102 | #' character vector coercible into a 4 column matrix using
103 | #' `matrix(x,ncol=4,byrow=TRUE)` command. The matrix/vector
104 | #' contains:
105 | #'
106 | #' Column 1: the \emph{long flag} name. A multi-character string.
107 | #'
108 | #' Column 2: \emph{short flag} alias of Column 1. A single-character
109 | #' string.
110 | #'
111 | #' Column 3: \emph{Argument} mask of the \emph{flag}. An integer.
112 | #' Possible values: 0=no argument, 1=required argument, 2=optional argument.
113 | #'
114 | #' Column 4: Data type to which the \emph{flag}'s argument shall be cast using
115 | #' [storage.mode()]. A multi-character string. This only considered
116 | #' for same-row Column 3 values of 1,2. Possible values: logical,
117 | #' integer, double, complex, character.
118 | #' If numeric is encountered then it will be converted to double.
119 | #'
120 | #' Column 5 (optional): A brief description of the purpose of the option.
121 | #'
122 | #' The terms \emph{option}, \emph{flag}, \emph{long flag}, \emph{short flag},
123 | #' and \emph{argument} have very specific meanings in the context of this
124 | #' document. Read the \dQuote{Description} section for definitions.
125 | #' @param opt This defaults to the return value of `commandArgs(TRUE)` unless
126 | #' `argv` is in the global environment in which case it uses that instead
127 | #' (this is for compatibility with `littler`).
128 | #'
129 | #' If R was invoked directly via the `R` command, this corresponds to all
130 | #' arguments passed to R after the `--args` flag.
131 | #'
132 | #' If R was invoked via the `Rscript` command, this corresponds to all
133 | #' arguments after the name of the R script file.
134 | #'
135 | #' Read about [commandArgs()] and \link{Rscript} to learn more.
136 | #' @param command The string to use in the usage message as the name of the
137 | #' script. See argument \emph{usage}.
138 | #' @param usage If `TRUE`, argument `opt` will be ignored and a usage
139 | #' statement (character string) will be generated and returned from `spec`.
140 | #' @param debug This is used internally to debug the `getopt()` function itself.
141 | #' @author Allen Day
142 | #' @keywords data
143 | #' @export
144 | #' @examples
145 | #' #!/path/to/Rscript
146 | #' library('getopt')
147 | #' # get options, using the spec as defined by the enclosed list.
148 | #' # we read the options from the default: commandArgs(TRUE).
149 | #' spec = matrix(c(
150 | #' 'verbose', 'v', 2, "integer",
151 | #' 'help' , 'h', 0, "logical",
152 | #' 'count' , 'c', 1, "integer",
153 | #' 'mean' , 'm', 1, "double",
154 | #' 'sd' , 's', 1, "double"
155 | #' ), byrow=TRUE, ncol=4)
156 | #' opt = getopt(spec)
157 | #'
158 | #' # if help was asked for print a friendly message
159 | #' # and exit with a non-zero error code
160 | #' if (!is.null(opt$help)) {
161 | #' cat(getopt(spec, usage = TRUE))
162 | #' q(status = 1)
163 | #' }
164 | #'
165 | #' # set some reasonable defaults for the options that are needed,
166 | #' # but were not specified.
167 | #' if (is.null(opt$mean)) opt$mean <- 0
168 | #' if (is.null(opt$sd)) opt$sd <- 1
169 | #' if (is.null(opt$count)) opt$count <- 10
170 | #' if (is.null(opt$verbose)) opt$verbose <- FALSE
171 | #'
172 | #' # print some progress messages to stderr, if requested.
173 | #' if (opt$verbose) write("writing...", stderr())
174 | #'
175 | #' # do some operation based on user input.
176 | #' cat(rnorm(opt$count, mean = opt$mean, sd = opt$sd), sep = "\n")
177 | #'
178 | #' # signal success and exit.
179 | #' # q(status=0)
180 | #' @import stats
181 | getopt <- function(spec = NULL, opt = NULL, command = get_Rscript_filename(), usage = FALSE, debug = FALSE) { # nolint
182 |
183 | # littler compatibility - map argv vector to opt
184 | if (is.null(opt)) {
185 | if (exists("argv", where = .GlobalEnv, inherits = FALSE)) {
186 | opt <- get("argv", envir = .GlobalEnv) # nocov
187 | } else {
188 | opt <- commandArgs(TRUE)
189 | }
190 | }
191 |
192 | ncol <- 4
193 | maxcol <- 6
194 | col_long_name <- 1
195 | col_short_name <- 2
196 | col_has_argument <- 3
197 | col_mode <- 4
198 | col_description <- 5
199 |
200 | flag_no_argument <- 0
201 | flag_required_argument <- 1
202 | flag_optional_argument <- 2
203 |
204 | result <- list()
205 | result$ARGS <- vector(mode = "character") # nolint
206 |
207 | # no spec. fail.
208 | if (is.null(spec)) {
209 | stop('argument "spec" must be non-null.')
210 |
211 | # spec is not a matrix. attempt to coerce, if possible. issue a warning.
212 | } else if (!is.matrix(spec)) {
213 | if (length(spec) / 4 == as.integer(length(spec) / 4)) {
214 | warning('argument "spec" was coerced to a 4-column (row-major) matrix. use a matrix to prevent the coercion')
215 | spec <- matrix(spec, ncol = ncol, byrow = TRUE)
216 | } else {
217 | stop('argument "spec" must be a matrix, or a character vector with length divisible by 4, rtfm.')
218 | }
219 |
220 | # spec is a matrix, but it has too few columns.
221 | } else if (dim(spec)[2] < ncol) {
222 | stop(paste('"spec" should have at least ', ncol, " columns.", sep = ""))
223 |
224 | # spec is a matrix, but it has too many columns.
225 | } else if (dim(spec)[2] > maxcol) {
226 | stop(paste('"spec" should have no more than ', maxcol, " columns.", sep = ""))
227 |
228 | # spec is a matrix, and it has some optional columns.
229 | } else if (dim(spec)[2] != ncol) {
230 | ncol <- dim(spec)[2]
231 | }
232 |
233 | # sanity check. make sure long names are unique, and short names are unique.
234 | if (length(unique(spec[, col_long_name])) != length(spec[, col_long_name])) {
235 | stop(paste("redundant long names for flags (column ", col_long_name, " of spec matrix).", sep = ""))
236 | }
237 | if (length(stats::na.omit(unique(spec[, col_short_name]))) != length(stats::na.omit(spec[, col_short_name]))) {
238 | stop(paste("redundant short names for flags (column ", col_short_name, " of spec matrix).", sep = ""))
239 | }
240 | # convert numeric type to double type
241 | spec[, 4] <- gsub("numeric", "double", spec[, 4])
242 |
243 | # if usage=TRUE, don't process opt, but generate a usage string from the data in spec
244 | if (usage) {
245 | ret <- ""
246 | ret <- paste(ret, "Usage: ", command, sep = "")
247 | for (j in 1:(dim(spec))[1]) {
248 | ret <- paste(ret, " [-[-", spec[j, col_long_name], "|", spec[j, col_short_name], "]", sep = "")
249 | if (spec[j, col_has_argument] == flag_no_argument) {
250 | ret <- paste(ret, "]", sep = "")
251 | } else if (spec[j, col_has_argument] == flag_required_argument) {
252 | ret <- paste(ret, " <", spec[j, col_mode], ">]", sep = "")
253 | } else if (spec[j, col_has_argument] == flag_optional_argument) {
254 | ret <- paste(ret, " [<", spec[j, col_mode], ">]]", sep = "")
255 | }
256 | }
257 | # include usage strings
258 | if (ncol >= 5) {
259 | max.long <- max(apply(cbind(spec[, col_long_name]), 1, function(x)length(strsplit(x, "")[[1]])))
260 | ret <- paste(ret, "\n", sep = "")
261 | for (j in 1:(dim(spec))[1]) {
262 | ret <- paste(ret, sprintf(paste(" -%s|--%-", max.long, "s %s\n", sep = ""),
263 | spec[j, col_short_name], spec[j, col_long_name], spec[j, col_description]
264 | ), sep = "")
265 | }
266 | }
267 | else {
268 | ret <- paste(ret, "\n", sep = "")
269 | }
270 | return(ret)
271 | }
272 |
273 | # XXX check spec validity here. e.g. column three should be convertible to integer
274 |
275 | i <- 1
276 |
277 | while (i <= length(opt)) {
278 | if (debug) print(paste("processing", opt[i]))
279 |
280 | current_flag <- 0 # XXX use NA
281 | optstring <- opt[i]
282 |
283 |
284 | # long flag
285 | if (substr(optstring, 1, 2) == "--") {
286 | if (debug) print(paste(" long option:", opt[i]))
287 |
288 | optstring <- substring(optstring, 3)
289 |
290 | this_flag <- NA
291 | this_argument <- NA
292 | kv <- strsplit(optstring, "=")[[1]]
293 | # if (!is.na(kv[2])) {
294 | if (grepl("=", optstring)) {
295 | this_flag <- kv[1]
296 | this_argument <- paste(kv[-1], collapse = "=")
297 | } else {
298 | this_flag <- optstring
299 | }
300 |
301 | rowmatch <- grep(this_flag, spec[, col_long_name], fixed = TRUE)
302 |
303 | # long flag is invalid, matches no options
304 | if (length(rowmatch) == 0) {
305 | stop(paste('long flag "', this_flag, '" is invalid', sep = ""))
306 |
307 | # long flag is ambiguous, matches too many options
308 | } else if (length(rowmatch) > 1) {
309 | # check if there is an exact match and use that
310 | rowmatch <- which(this_flag == spec[, col_long_name])
311 | if (length(rowmatch) == 0) {
312 | stop(paste('long flag "', this_flag, '" is ambiguous', sep = ""))
313 | }
314 | }
315 |
316 | # if we have an argument
317 | if (!is.na(this_argument)) {
318 | # if we can't accept the argument, bail out
319 | if (spec[rowmatch, col_has_argument] == flag_no_argument) {
320 | stop(paste('long flag "', this_flag, '" accepts no arguments', sep = ""))
321 |
322 | # otherwise assign the argument to the flag
323 | } else {
324 | mode <- spec[rowmatch, col_mode]
325 | warning_msg <- tryCatch(storage.mode(this_argument) <- mode,
326 | warning = function(w) warning(paste(mode, "expected, got", dQuote(this_argument))))
327 | if (is.na(this_argument) && !grepl("expected, got", warning_msg)) {
328 | warning(paste("long flag", this_flag, "given a bad argument"))
329 | }
330 | result[spec[rowmatch, col_long_name]] <- this_argument
331 | i <- i + 1
332 | next
333 | }
334 |
335 | # otherwise, we don't have an argument
336 | } else {
337 | # nolint start
338 | # if we require an argument, bail out
339 | ### if (spec[rowmatch, col_has_argument] == flag_required_argument) {
340 | ### stop(paste('long flag "', this_flag, '" requires an argument', sep = ""))
341 |
342 | # long flag has no attached argument. set flag as present.
343 | # set current_flag so we can peek ahead later and consume the argument if it's there
344 | # nolint end
345 | ###} else {
346 | result[spec[rowmatch, col_long_name]] <- TRUE
347 | current_flag <- rowmatch
348 | ###}
349 | }
350 |
351 | # short flag(s)
352 | } else if (substr(optstring, 1, 1) == "-") {
353 | if (debug) print(paste(" short option:", opt[i]))
354 |
355 | these_flags <- strsplit(optstring, "")[[1]]
356 |
357 | done <- FALSE
358 | for (j in 2:length(these_flags)) {
359 | this_flag <- these_flags[j]
360 | rowmatch <- grep(this_flag, spec[, col_short_name], fixed = TRUE)
361 |
362 | # short flag is invalid, matches no options
363 | if (length(rowmatch) == 0) {
364 | stop(paste('short flag "', this_flag, '" is invalid', sep = ""))
365 |
366 | # short flag has an argument, but is not the last in a compound flag string
367 | } else if (j < length(these_flags) & spec[rowmatch, col_has_argument] == flag_required_argument) {
368 | stop(paste('short flag "', this_flag, '" requires an argument, but has none', sep = ""))
369 |
370 | # short flag has no argument, flag it as present
371 | } else if (spec[rowmatch, col_has_argument] == flag_no_argument) {
372 | result[spec[rowmatch, col_long_name]] <- TRUE
373 | done <- TRUE
374 |
375 | # can't definitively process this_flag flag yet, need to see if next option is an argument or not
376 | } else {
377 | result[spec[rowmatch, col_long_name]] <- TRUE
378 | current_flag <- rowmatch
379 | done <- FALSE
380 | }
381 | }
382 | if (done) {
383 | i <- i + 1
384 | next
385 | }
386 | }
387 |
388 | # invalid opt
389 | if (current_flag == 0) {
390 | stop(paste('"', optstring, '" is not a valid option, or does not support an argument', sep = ""))
391 |
392 | # nolint start
393 | # TBD support for positional args
394 | # if (debug) print(paste('"', optstring, '" not a valid option. It is appended to getopt(...)$ARGS', sep = ""))
395 | # result$ARGS = append(result$ARGS, optstring)
396 | # nolint end
397 |
398 | # some dangling flag, handle it
399 | } else if (current_flag > 0) {
400 | if (debug) print(" dangling flag")
401 | if (length(opt) > i) {
402 | peek_optstring <- opt[i + 1]
403 | if (debug) print(paste(' peeking ahead at: "', peek_optstring, '"', sep = ""))
404 |
405 | # got an argument. attach it, increment the index, and move on to the next option.
406 | # we don't allow arguments beginning with "-" UNLESS specfile indicates the value is an "integer" or "double",
407 | # in which case we allow a leading dash (and verify trailing digits/decimals).
408 | if (substr(peek_optstring, 1, 1) != "-" |
409 | # match negative double
410 | (substr(peek_optstring, 1, 1) == "-"
411 | & regexpr("^-[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$", peek_optstring) > 0
412 | & spec[current_flag, col_mode] == "double"
413 | ) |
414 | # match negative integer
415 | (substr(peek_optstring, 1, 1) == "-"
416 | & regexpr("^-[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$", peek_optstring) > 0
417 | & spec[current_flag, col_mode] == "integer"
418 | )
419 | ) {
420 | if (debug) print(paste(" consuming argument *", peek_optstring, "*", sep = ""))
421 |
422 | mode <- spec[current_flag, col_mode]
423 | tryCatch(storage.mode(peek_optstring) <- mode,
424 | warning = function(w) warning(paste(mode, "expected, got", dQuote(peek_optstring))))
425 | result[spec[current_flag, col_long_name]] <- peek_optstring
426 | i <- i + 1
427 |
428 | # a lone dash
429 | } else if (substr(peek_optstring, 1, 1) == "-" & length(strsplit(peek_optstring, "")[[1]]) == 1) {
430 | if (debug) print(' consuming "lone dash" argument')
431 | mode <- spec[current_flag, col_mode]
432 | tryCatch(storage.mode(peek_optstring) <- mode,
433 | warning = function(w) warning(paste(mode, "expected, got", dQuote(peek_optstring)))) # nocov
434 | result[spec[current_flag, col_long_name]] <- peek_optstring
435 | i <- i + 1
436 |
437 | # no argument
438 | } else {
439 | if (debug) print(" no argument!")
440 |
441 | # if we require an argument, bail out
442 | if (spec[current_flag, col_has_argument] == flag_required_argument) {
443 | stop(paste('flag "', this_flag, '" requires an argument', sep = ""))
444 |
445 | # otherwise set flag as present.
446 | } else if (
447 | spec[current_flag, col_has_argument] == flag_optional_argument |
448 | spec[current_flag, col_has_argument] == flag_no_argument
449 | ) {
450 | x <- TRUE
451 | storage.mode(x) <- spec[current_flag, col_mode]
452 | result[spec[current_flag, col_long_name]] <- x
453 | } else {
454 | stop(paste("This should never happen.", # nocov
455 | "Is your spec argument correct? Maybe you forgot to set", # nocov
456 | "ncol=4, byrow=TRUE in your matrix call?")) # nocov
457 | }
458 | }
459 | # trailing flag without required argument
460 | } else if (spec[current_flag, col_has_argument] == flag_required_argument) {
461 | stop(paste('flag "', this_flag, '" requires an argument', sep = ""))
462 |
463 | # trailing flag without optional argument
464 | } else if (spec[current_flag, col_has_argument] == flag_optional_argument) {
465 | x <- TRUE
466 | storage.mode(x) <- spec[current_flag, col_mode]
467 | result[spec[current_flag, col_long_name]] <- x
468 |
469 | # trailing flag without argument
470 | } else if (spec[current_flag, col_has_argument] == flag_no_argument) {
471 | x <- TRUE
472 | storage.mode(x) <- spec[current_flag, col_mode]
473 | result[spec[current_flag, col_long_name]] <- x
474 | } else {
475 | stop("this should never happen (2). please inform the author.") # nocov
476 | }
477 | } # no dangling flag, nothing to do.
478 |
479 | i <- i + 1
480 | }
481 | return(result)
482 | }
483 |
--------------------------------------------------------------------------------