Repository: carlobaldassi/ArgParse.jl Branch: master Commit: db1ad6f67b31 Files: 50 Total size: 229.1 KB Directory structure: gitextract_2653z8hp/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── TagBot.yml │ ├── ci.yml │ └── documentation.yml ├── LICENSE.md ├── Project.toml ├── README.md ├── docs/ │ ├── .gitignore │ ├── Project.toml │ ├── make.jl │ └── src/ │ ├── arg_table.md │ ├── conflicts.md │ ├── custom.md │ ├── details.md │ ├── import.md │ ├── index.md │ ├── parse_args.md │ └── settings.md ├── examples/ │ ├── argparse_example1.jl │ ├── argparse_example2.jl │ ├── argparse_example3.jl │ ├── argparse_example4.jl │ ├── argparse_example5.jl │ ├── argparse_example6.jl │ ├── argparse_example7.jl │ └── argparse_example8.jl ├── src/ │ ├── ArgParse.jl │ ├── common.jl │ ├── deprecated.jl │ ├── parsing.jl │ └── settings.jl └── test/ ├── Project.toml ├── argparse_test01.jl ├── argparse_test02.jl ├── argparse_test03.jl ├── argparse_test04.jl ├── argparse_test05.jl ├── argparse_test06.jl ├── argparse_test07.jl ├── argparse_test08.jl ├── argparse_test09.jl ├── argparse_test10.jl ├── argparse_test11.jl ├── argparse_test12.jl ├── argparse_test13.jl ├── argparse_test14.jl ├── args-file1 ├── args-file2 ├── common.jl └── runtests.jl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" # Location of package manifests schedule: interval: "monthly" ================================================ FILE: .github/workflows/TagBot.yml ================================================ name: TagBot on: issue_comment: types: - created workflow_dispatch: jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} ssh: ${{ secrets.DOCUMENTER_KEY }} ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - master tags: '*' pull_request: schedule: # Run CI against `master` every Sunday - cron: '0 0 * * 0' jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.version == 'nightly' }} strategy: matrix: version: - '1.0' # Oldest supported version - '1' # Latest release - 'nightly' os: - ubuntu-latest - macOS-latest - windows-latest arch: - x64 - x86 exclude: # Remove some configurations from the build matrix to reduce CI time. # See https://github.com/marketplace/actions/setup-julia-environment - os: macOS-latest arch: x86 - os: windows-latest arch: x86 steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - uses: julia-actions/cache@v3 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v6 with: files: lcov.info ================================================ FILE: .github/workflows/documentation.yml ================================================ name: Documentation on: push: branches: - master tags: '*' pull_request: jobs: build: permissions: actions: write contents: write pull-requests: read statuses: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v2 with: version: '1.12' - uses: julia-actions/cache@v3 - name: Install dependencies shell: julia --color=yes --project=docs {0} run: | using Pkg Pkg.develop(PackageSpec(path=pwd())) Pkg.instantiate() - name: Build and deploy run: julia --color=yes --project=docs docs/make.jl env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key ================================================ FILE: LICENSE.md ================================================ The ArgParse Julia module is licensed under the MIT License: > Copyright (c) 2012: Carlo Baldassi > > Permission is hereby granted, free of charge, to any person obtaining > a copy of this software and associated documentation files (the > "Software"), to deal in the Software without restriction, including > without limitation the rights to use, copy, modify, merge, publish, > distribute, sublicense, and/or sell copies of the Software, and to > permit persons to whom the Software is furnished to do so, subject to > the following conditions: > > The above copyright notice and this permission notice shall be > included in all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Portions of this software are derived from Python 3.5.1 code, and therefore is licensed under the permissive but slightly more strict Python Software Foundation license, and is partially copyright 2001-2016 Python Software Foundation; All Rights Reserved. > 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and > the Individual or Organization ("Licensee") accessing and otherwise using Python > 3.5.1 software in source or binary form and its associated documentation. > > 2. Subject to the terms and conditions of this License Agreement, PSF hereby > grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, > analyze, test, perform and/or display publicly, prepare derivative works, > distribute, and otherwise use Python 3.5.1 alone or in any derivative > version, provided, however, that PSF's License Agreement and PSF's notice of > copyright, i.e., "Copyright © 2001-2016 Python Software Foundation; All Rights > Reserved" are retained in Python 3.5.1 alone or in any derivative version > prepared by Licensee. > > 3. In the event Licensee prepares a derivative work that is based on or > incorporates Python 3.5.1 or any part thereof, and wants to make the > derivative work available to others as provided herein, then Licensee hereby > agrees to include in any such work a brief summary of the changes made to Python > 3.5.1. > > 4. PSF is making Python 3.5.1 available to Licensee on an "AS IS" basis. > PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF > EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR > WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE > USE OF PYTHON 3.5.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. > > 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.5.1 > FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF > MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.5.1, OR ANY DERIVATIVE > THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. > > 6. This License Agreement will automatically terminate upon a material breach of > its terms and conditions. > > 7. Nothing in this License Agreement shall be deemed to create any relationship > of agency, partnership, or joint venture between PSF and Licensee. This License > Agreement does not grant permission to use PSF trademarks or trade name in a > trademark sense to endorse or promote products or services of Licensee, or any > third party. > > 8. By copying, installing or otherwise using Python 3.5.1, Licensee agrees > to be bound by the terms and conditions of this License Agreement. ================================================ FILE: Project.toml ================================================ name = "ArgParse" uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" author = ["Carlo Baldassi "] version = "1.2.0" [deps] Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" TextWrap = "b718987f-49a8-5099-9789-dcd902bef87d" [compat] TextWrap = "0.1.4, 1" julia = "1" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Test"] [workspace] projects = ["docs"] ================================================ FILE: README.md ================================================ # ArgParse.jl [![DOCS][docs-img]][docs-url] [![CI][CI-img]][CI-url] [![CODECOV][codecov-img]][codecov-url] ArgParse.jl is a package for parsing command-line arguments to [Julia][julia] programs. ### Installation and usage To install the module, use Julia's package manager: start pkg mode by pressing `]` and then enter: ``` (v1.5) pkg> add ArgParse ``` The module can then be loaded like any other Julia module: ``` julia> using ArgParse ``` ### Documentation - The manual is [HERE][docs-url]. - See also the examples in the [examples directory](examples). ## Changes in release 1.2.0 * Add options to control the help text formatting ([#132][PR132]) * Allow defaults that can be converted into the target argument type ([#133][PR133]) ## Changes in release 1.1.5 * Fix ambiguity with julia 1.11 new `wrap` function (see [#128][PR128]) * Throw a new `ArgParseSettingError` for all settings-related errors * Fixed some tests ## Changes in release 1.1.4 * Fix in @project_version macro (see [#107][PR107]) ## Changes in release 1.1.3 * Added a @project_version macro (see [#106][PR106]) ## Changes in release 1.1.2 * Faster startup time by disabling optimizations/inference (see [#104][PR104]) ## Changes in release 1.1.1 * Fixed the case when using symbol keys, commands are not required, no command is provided ## Changes in release 1.1.0 * Try using the constructor for types that don't define a `convert` method from `AbstractString` ## Changes in release 1.0.1 * Small fixes in docs ## Changes in release 1.0.0 * Drop support for Julia versions v0.6/v0.7 * Renamed a few functions and macros (old versions can be used but produce deprecation warnings): + `@add_arg_table` → `@add_arg_table!` + `add_arg_table` → `add_arg_table!` + `add_arg_group` → `add_arg_group!` + `set_default_arg_group` → `set_default_arg_group!` + `import_settings` → `import_settings!`. The signature of this function has also changed: `args_only` is now a keyword argument * Parsing does not exit julia by default when in interactive mode now * Added mutually-exclusive and/or required argument groups * Added command aliases ## Changes in release 0.6.2 * Fix a remaining compatibility issue (`@warn`) ## Changes in release 0.6.1 * Testing infrastructure update, tiny docs fixes ## Changes in release 0.6.0 * Added support for Julia v0.7, dropped support for Julia v0.5. * Added `exit_after_help` setting to control whether to exit julia after help/version info is displayed (which is still the defult) or to just abort the parsing and return `nothing` instead. ## Changes in release 0.5.0 * Added support for Julia v0.6, dropped support for Julia v0.4. * The default output type is now `Dict{String,Any}`, as stated in the docs, rather than `Dict{AbstractString,Any}`. * Added docstrings, moved documentation to Documenter.jl ## Changes in release 0.4.0 ### New features * Added support for vectors of METAVAR names (see [#33][PR33]) ### Other changes * Support for Julia v0.3 was dropped. ## Changes in release 0.3.1 ### New available settings * `fromfile_prexif_chars` (see [#27][PR27]) * `preformatted_desciption`/`preformatted_epilog` (see [#28][PR28]) ## Changes in release 0.3.0 ### Breaking changes Upgrading from versions 0.2.X to 0.3.X, the following API changes were made, which may break existing code: * Option arguments are no longer evaluated by default. This is for security reasons. Evaluation can be forced on a per-option basis with the `eval_arg=true` setting (although this is discuraged). * The syntax of the `add_arg_table` function has changed, it now takes a `Dict` object instead of an `@options` opbject, since the dependency on the Options.jl module was removed. (The `@add_arg_table` macro is unchanged though.) ### Other changes * Documented that overloading the function `ArgParse.parse_item` can be used to instruct ArgParse on how to parse custom types. Parse error reporting was also improved * Removed dependecy on the Options.jl module * Enabled precompilation on Julia 0.4 [Julia]: http://julialang.org [docs-img]: https://img.shields.io/badge/docs-stable-blue.svg [docs-url]: https://carlobaldassi.github.io/ArgParse.jl/stable [codecov-img]: https://codecov.io/gh/carlobaldassi/ArgParse.jl/branch/master/graph/badge.svg [codecov-url]: https://codecov.io/gh/carlobaldassi/ArgParse.jl [CI-img]: https://github.com/carlobaldassi/ArgParse.jl/actions/workflows/ci.yml/badge.svg [CI-url]: https://github.com/carlobaldassi/ArgParse.jl/actions/workflows/ci.yml [PR27]: https://github.com/carlobaldassi/ArgParse.jl/pull/27 [PR28]: https://github.com/carlobaldassi/ArgParse.jl/pull/28 [PR33]: https://github.com/carlobaldassi/ArgParse.jl/pull/33 [PR104]: https://github.com/carlobaldassi/ArgParse.jl/pull/104 [PR106]: https://github.com/carlobaldassi/ArgParse.jl/pull/106 [PR107]: https://github.com/carlobaldassi/ArgParse.jl/pull/107 [PR128]: https://github.com/carlobaldassi/ArgParse.jl/pull/128 [PR132]: https://github.com/carlobaldassi/ArgParse.jl/pull/132 [PR133]: https://github.com/carlobaldassi/ArgParse.jl/pull/133 ================================================ FILE: docs/.gitignore ================================================ Manifest.toml build/ ================================================ FILE: docs/Project.toml ================================================ [deps] ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" [compat] Documenter = "1" ================================================ FILE: docs/make.jl ================================================ using Documenter, ArgParse CIbuild = get(ENV, "CI", nothing) == "true" makedocs( modules = [ArgParse], format = Documenter.HTML(prettyurls = CIbuild), sitename = "ArgParse.jl", pages = Any[ "Home" => "index.md", "Manual" => [ "parse_args.md", "settings.md", "arg_table.md", "import.md", "conflicts.md", "custom.md", "details.md" ] ], warnonly = [:missing_docs], ) deploydocs( repo = "github.com/carlobaldassi/ArgParse.jl.git", ) ================================================ FILE: docs/src/arg_table.md ================================================ # Argument table The argument table is used to store allowed arguments and options in an [`ArgParseSettings`](@ref) object. Each entry of the table consist of an argument name and a list of argument settings, e.g.: ```julia "--verbose" help = "verbose output" action = :store_true ``` There are two very similar methods to populate a table: ```@docs @add_arg_table! ``` ```@docs add_arg_table! ``` ## Argument names Argument names are strings or, in the case of options, lists of strings. An argument is an option if it begins with a `'-'` character, otherwise it'a positional argument. A single `'-'` introduces a short option, which must consist of a single character; long options begin with `"--"` instead. Positional argument names can be any string, except all-uppercase strings between `'%'` characters, which are reserved (e.g. `"%COMMAND%"`). Option names can contain any character except `'='`, whitespaces and non-breakable spaces. Depending on the value of the `add_help` and `add_version` settings, options `--help`, `-h` and `--version` may be reserved. If the `allow_ambiguous_opts` setting is `false`, some characters are not allowed as short options: all digits, the dot, the underscore and the opening parethesis (e.g. `-1`, `-.`, `-_`, `-(`). For positional arguments, the argument name will be used as the key in the `Dict` object returned by the [`parse_args`](@ref) function. For options, it will be used to produce a default key in case a `dest_name` is not explicitly specified in the table entry, using either the first long option name in the list or the first short option name if no long options are present. For example: | argument name | default `dest_name` | |:-----------------------------|:------------------------| | `"--long"` | `"long"` | | `"--long", "-s"` | `"long"` | | `"-s", "--long1", "--long2"` | `"long1"` | | `"-s", "-x"` | `"s"` | In case the `autofix_names` setting is `true` (it is `false` by default), dashes in the names of arguments and long options will be converted to underscores: for example, `"--my-opt"` will yield `"my_opt"` as the default `dest_name`. The argument name is also used to generate a default metavar in case `metavar` is not explicitly set in the table entry. The rules are the same used to determine the default `dest_name`, but for options the result will be uppercased (e.g. `"--long"` will become `LONG`). Note that this poses additional constraints on the positional argument names (e.g. whitespace is not allowed in metavars). ## Argument entry settings Argument entry settings determine all aspects of an argument's behavior. Some settings combinations are contradictory and will produce an error (e.g. using both `action = :store_true` and `nargs = 1`, or using `action = :store_true` with a positional argument). Also, some settings are only meaningful under some conditions (e.g. passing a `metavar` to a flag-like option does not make sense) and will be ignored with a warning (unless the `suppress_warnings` general setting is `true`). This is the list of all available settings: * `nargs` (default = `'A'`): the number of extra command-line tokens parsed with the entry. See the section [Available actions and nargs values](@ref) for a complete desctiption. * `action`: the action performed when the argument is parsed. It can be passed as a `String` or as a `Symbol` (e.g. both `:store_arg` and `"store_arg"` are accepted). The default action is `:store_arg` unless `nargs` is `0`, in which case the default is `:store_true`. See the section [Available actions and nargs values](@ref) for a list of all available actions and a detailed explanation. * `arg_type` (default = `Any`): the type of the argument. Only makes sense with non-flag arguments. Only works out-of-the-box with string, symbol and number types, but see the section [Parsing to custom types](@ref) for details on how to make it work for general types (including user-defined ones). * `default` (default = `nothing`): the default value if the option or positional argument is not parsed. Only makes sense with non-flag arguments, or when the action is `:store_const` or `:append_const`. Unless it's `nothing`, it must be consistent with `arg_type` and `range_tester`. * `constant` (default = `nothing`): this value is used by the `:store_const` and `:append_const` actions, or when `nargs = '?'` and the option argument is not provided. * `required` (default = `false`): determines if an argument is required (this setting is ignored by flags, which are always optional, and in general should be avoided for options if possible). * `range_tester` (default = `x->true`): a function returning `true` if an argument is allowed and otherwise returning `false` (e.g. you could use `arg_type = Integer` and `range_tester = isodd` to allow only odd integer values) * `dest_name` (default = auto-generated): the key which will be associated with the argument in the `Dict` object returned by `parse_args`. The auto-generation rules are explained in the [Argument names](@ref) section. Multiple arguments can share the same destination, provided their actions and types are compatible. * `help` (default = `""`): the help string which will be shown in the auto-generated help screen. It's a `String` which will be automaticaly formatted; also, `arg_type` and `default` will be automatically appended to it if provided. * `metavar` (default = auto-generated): a token which will be used in usage and help screens to describe the argument syntax. For positional arguments, it will also be used as an identifier in all other messages (e.g. in reporting errors), therefore it must be unique. For optional arguments, if `nargs > 1` then `metavar` can be a `Vector` of `String`s of length `nargs`. The auto-generations rules are explained in the [Argument names](@ref) section. * `force_override`: if `true`, conflicts are ignored when adding this entry in the argument table (see also the [Conflicts and overrides](@ref) section). By default, it follows the general `error_on_conflict` settings. * `group`: the option group to which the argument will be assigned to (see the [Argument groups](@ref) section). By default, the current default group is used if specified, otherwise the assignment is automatic. * `eval_arg` (default: `false`): if `true`, the argument will be parsed as a Julia expression and evaluated, which means that for example `"2+2"` will yield the integer `4` rather than a string. Note that this is a security risk for outside-facing programs and should generally be avoided: overload `ArgParse.parse_item` instead (see the section [Parsing to custom types](@ref)). Only makes sense for non-flag arguments. ## Available actions and nargs values The `nargs` and `action` argument entry settings are used together to determine how many tokens will be parsed from the command line and what action will be performed on them. The `nargs` setting can be a number or a character; the possible values are: * `'A'`: automatic, i.e. inferred from the action (this is the default). In practice, it means `0` for options with flag actions (see the actions categorization below, in this section) and `1` for options with non-flag actions (but it's different from using an explicit `1` because the result is not stored in a `Vector`). * `0`: this is the only possibility (besides `'A'`) for flag actions, and it means no extra tokens will be parsed from the command line. If `action` is not specified, setting `nargs` to `0` will make `action` default to `:store_true`. * a positive integer number `N`: exactly `N` tokens will be parsed from the command-line, and stored into a `Vector` of length `N`. Note that `nargs=1` produces a `Vector` of one item. * `'?'`: optional, i.e. a token will only be parsed if it does not look like an option (see the [Parsing details](@ref) section for a discussion of how exactly this is established). If the option string is not given, the `default` argument value will be used. If the option string is given but not followed by an option parameter, the `constant` argument value will be used instead. This only makes sense with options. * `'*'`: any number, i.e. all subsequent tokens are stored into a `Vector`, up until a token which looks like an option is encountered, or all tokens are consumed. * `'+'`: like `'*'`, but at least one token is required. * `'R'`: all remainder tokens, i.e. like `'*'` but it does not stop at options. Actions can be categorized in many ways; one prominent distinction is flag vs. non-flag: some actions are for options which take no argument (i.e. flags), all others (except `command`, which is special) are for other options and positional arguments: * flag actions are only compatible with `nargs = 0` or `nargs = 'A'` * non-flag actions are not compatible with `nargs = 0`. In other words, all flags (takes no argument) are options (starts with a dash), but not all options (starts with a dash) are flags (takes no argument). This is the list of all available actions (in each example, suppose we defined `settings = ArgParseSettings()`): * `store_arg` (non-flag): store the argument. This is the default unless `nargs` is `0`. Example: ```julia-repl julia> @add_arg_table!(settings, "arg", action => :store_arg); julia> parse_args(["x"], settings) Dict{String,Any} with 1 entry: "arg" => "x" ``` The result is a vector if `nargs` is a non-zero number, or one of `'*'`, `'+'`, `'R'`: ```julia-repl julia> @add_arg_table!(settings, "arg", action => :store_arg, nargs => 2); julia> parse_args(["x", "y"], settings) Dict{String,Any} with 1 entry: "arg" => Any["x","y"] ``` * `store_true` (flag): store `true` if given, otherwise `false`. Example: ```julia-repl julia> @add_arg_table!(settings, "-v", action => :store_true); julia> parse_args([], settings) Dict{String,Any} with 1 entry: "v" => false julia> parse_args(["-v"], settings) Dict{String,Any} with 1 entry: "v" => true ``` * `store_false` (flag): store `false` if given, otherwise `true`. Example: ```julia-repl julia> @add_arg_table!(settings, "-v", action => :store_false); julia> parse_args([], settings) Dict{String,Any} with 1 entry: "v" => true julia> parse_args(["-v"], settings) Dict{String,Any} with 1 entry: "v" => false ``` * `store_const` (flag): store the value passed as `constant` in the entry settings if given, otherwise `default`. Example: ```julia-repl julia> @add_arg_table!(settings, "-v", action => :store_const, constant => 1, default => 0); julia> parse_args([], settings) Dict{String,Any} with 1 entry: "v" => 0 julia> parse_args(["-v"], settings) Dict{String,Any} with 1 entry: "v" => 1 ``` * `append_arg` (non-flag): append the argument to the result. Example: ```julia-repl julia> @add_arg_table!(settings, "-x", action => :append_arg); julia> parse_args(["-x", "1", "-x", "2"], settings) Dict{String,Any} with 1 entry: "x" => Any["1","2"] ``` The result will be a `Vector{Vector}` if `nargs` is a non-zero number, or one of `'*'`, `'+'`, `'R'`: ```julia-repl julia> @add_arg_table!(settings, "-x", action => :append_arg, nargs => '*'); julia> parse_args(["-x", "1", "2", "-x", "3"], settings) Dict{String,Any} with 1 entry: "x" => Array{Any,1}[Any["1","2"],Any["3"]] ``` * `append_const` (flag): append the value passed as `constant` in the entry settings. Example: ```julia-repl julia> @add_arg_table!(settings, "-x", action => :append_const, constant => 1); julia> parse_args(["-x", "-x", "-x"], settings) Dict{String,Any} with 1 entry: "x" => Any[1,1,1] ``` * `count_invocations` (flag): increase a counter; the final result will be the number of times the option was invoked. Example: ```julia-repl julia> @add_arg_table!(settings, "-x", action => :count_invocations); julia> parse_args(["-x", "-x", "-x"], settings) Dict{String,Any} with 1 entry: "x" => 3 ``` * `show_help` (flag): show the help screen and exit. This is useful if the `add_help` general setting is `false`. Example: ```julia-repl julia> @add_arg_table!(settings, "-x", action => :show_help); julia> parse_args(["-x"], settings) usage: [-x] optional arguments: -x ``` * `show_version` (flag): show the version information and exit. This is useful if the `add_version` general setting is `false`. Example: ```julia-repl julia> settings.version = "1.0"; julia> @add_arg_table!(settings, "-v", action => :show_version); julia> parse_args(["-v"], settings) 1.0 ``` * `command` (special): the argument or option is a command, i.e. it starts a sub-parsing session (see the [Commands](@ref) section). ## Commands Commands are a special kind of arguments which introduce sub-parsing sessions as soon as they are encountered by `parse_args` (and are therefore mutually exclusive). The `ArgParse` module allows commands to look both as positional arguments or as options, with minor differences between the two. Unlike actual positional arguments, commands that *look* like positional arguments can have extra names (aliases). Commands are introduced by the `action = :command` setting in the argument table. Suppose we save the following script in a file called `cmd_example.jl`: ```julia using ArgParse function parse_commandline() s = ArgParseSettings() @add_arg_table! s begin "cmd1", "C" help = "first command" action = :command "cmd2", "K" help = "second command" action = :command end return parse_args(s) end parsed_args = parse_commandline() println(parsed_args) ``` Invoking the script from the command line, we would get the following help screen: ```text $ julia cmd_example.jl --help usage: cmd_example.jl [-h] {cmd1|cmd2} commands: cmd1 first command (aliases: C) cmd2 second command (aliases: K) optional arguments: -h, --help show this help message and exit ``` If commands are present in the argument table, `parse_args` will set the special key `"%COMMAND%"` in the returned `Dict` and fill it with the invoked command (or `nothing` if no command was given): ```text $ julia cmd_example.jl cmd1 Dict("%COMMAND%"=>"cmd1", "cmd1"=>Dict()) ``` This is unless `parse_args` is invoked with `as_symbols=true`, in which case the special key becomes `:_COMMAND_`. (In that case, no other argument is allowed to use `_COMMAND_` as its `dest_name`, or an error will be raised.) Aliases are recognized when parsing, but the returned `Dict` will always use the command's name (the first entry in the table): ```text $ julia cmd_example.jl C Dict("%COMMAND%"=>"cmd1", "cmd1"=>Dict()) ``` Since commands introduce sub-parsing sessions, an additional key will be added for the called command (`"cmd1"` in this case) whose associated value is another `Dict{String, Any}` containing the result of the sub-parsing (in the above case it's empty). In fact, with the default settings, commands have their own help screens: ```text $ julia cmd_example.jl cmd1 --help usage: cmd_example.jl cmd1 [-h] optional arguments: -h, --help show this help message and exit ``` The argument settings and tables for commands can be accessed by using a dict-like notation, i.e. `settings["cmd1"]` is an `ArgParseSettings` object specific to the `"cmd1"` command. Therefore, to populate a command sub-argument-table, simply use `@add_arg_table!(settings["cmd1"], table...)` and similar. These sub-settings are created when a command is added to the argument table, and by default they inherit their parent general settings except for the `prog` setting (which is auto-generated, as can be seen in the above example) and the `description`, `epilog` and `usage` settings (which are left empty). Commands can also have sub-commands. By default, if commands exist, they are required; this can be avoided by setting the `commands_are_required = false` general setting. The only meaningful settings for commands in an argument entry besides `action` are `help`, `force_override`, `group` and (for flags only) `dest_name`. The only differences between positional-arguments-like and option-like commands are in the way they are parsed, and the fact that options accept a `dest_name` setting. Note that short-form option-like commands will be still be recognized in the middle of a short options group and trigger a sub-parsing session: for example, if an option `-c` is associated to a command, then `-xch` will parse option `-x` according to the parent settings, and option `-h` according to the command sub-settings. ## Argument groups By default, the auto-generated help screen divides arguments into three groups: commands, positional arguments and optional arguments, displayed in that order. Example: ```@setup args using ArgParse ``` ```@repl args settings = ArgParseSettings(); @add_arg_table! settings begin "--opt" "arg" required = true "cmd1" action = :command "cmd2" action = :command end; settings.exit_after_help = false # hide parse_args(["--help"], settings) ``` It is possible to partition the arguments differently by defining and using customized argument groups. Groups of options can also be declared to be mutually exclusive, meaning that no more than one of the options in the group can be provided. A group can also be declared to be required, meaning that at least one argument in the group needs to be provided. ```@docs add_arg_group! ``` ```@docs set_default_arg_group! ``` Besides setting a default group with `add_arg_group!` and `set_default_group!`, it's also possible to assign individual arguments to a group by using the `group` setting in the argument table entry, which follows the same rules as `set_default_group!`. Note that if the `add_help` or `add_version` general settings are `true`, the `--help, -h` and `--version` options will always be added to the `optional` group. ## Argument table styles Here are some examples of styles for the [`@add_arg_table!`](@ref) marco and [`add_arg_table!`](@ref) function invocation: ```julia @add_arg_table! settings begin "--opt", "-o" help = "an option" "arg" help = "a positional argument" end @add_arg_table!(settings , ["--opt", "-o"] , help => "an option" , "arg" , help => "a positional argument" ) @add_arg_table! settings begin (["--opt", "-o"]; help = an option) ("arg"; help = "a positional argument") end @add_arg_table!(settings, ["-opt", "-o"], begin help = "an option" end, "arg", begin help = "a positional argument" end) add_arg_table!(settings, ["-opt", "-o"], Dict(:help => "an option"), "arg" , Dict(:help => "a positional argument") ) ``` One restriction is that groups introduced by `begin...end` blocks or semicolon-separated lists between parentheses cannot introduce argument names unless the first item in the block is an argument name. ================================================ FILE: docs/src/conflicts.md ================================================ # Conflicts and overrides Conflicts between arguments, be them options, positional arguments or commands, can arise for a variety of reasons: * Two options have the same name (either long or short) * Two arguments have the same destination key, but different types (e.g. one is `Any` and the other `String`) * Two arguments have the same destination key, but incompatible actions (e.g. one does `:store_arg` and the other `:append_arg`) * Two positional arguments have the same metavar (and are therefore indistinguishable in the usage and help screens and in error messages) * An argument's destination key is the same as a command name * Two commands with the same name are given, but one has a long-option form (e.g. `test` and `--test`) * A command alias is equal to another command's name or alias When the general setting `error_on_conflict` is `true`, or any time the specific `force_override` table entry setting is `false`, any of the above conditions leads to an error. On the other hand, setting `error_on_conflict` to `false`, or `force_override` to `true`, will try to force the resolution of most of the conflicts in favor of the newest added entry. The general rules are the following: * In case of duplicate options, all conflicting forms of the older options are removed; if all forms of an option are removed, the option is deleted entirely * In case of duplicate destination key and incompatible types or actions, the older argument is deleted * In case of duplicate positional arguments metavars, the older argument is deleted * A command can override an argument with the same destination key * However, an argument can never override a command; neither can a command override another command when added with `@add_arg_table!` (compatible commands are merged by [`import_settings!`](@ref) though) * Conflicting command aliases are removed ================================================ FILE: docs/src/custom.md ================================================ # Parsing to custom types If you specify an `arg_type` setting (see the [Argument entry settings](@ref) section) for an option or an argument, `parse_args` will try to parse it, i.e. to convert the string to the specified type. For `Number` types, Julia's built-in `parse` function will be used. For other types, first `convert` and then the type's constructor will be tried. In order to extend this functionality, e.g. to user-defined custom types, without adding methods to `convert` or the constructor, you can overload the `ArgParse.parse_item` function. Example: ```julia struct CustomType val::Int end function ArgParse.parse_item(::Type{CustomType}, x::AbstractString) return CustomType(parse(Int, x)) end ``` Note that the second argument needs to be of type `AbstractString` to avoid ambiguity errors. Also note that if your type is parametric (e.g. `CustomType{T}`), you need to overload the function like this: `function ArgParse.parse_item(::Type{CustomType{T}}, x::AbstractString) where {T}`. ================================================ FILE: docs/src/details.md ================================================ # Parsing details During parsing, `parse_args` must determine whether an argument is an option, an option argument, a positional argument, or a command. The general rules are explained in the [`parse_args`](@ref) documentation, but ambiguities may arise under particular circumstances. In particular, negative numbers like `-1` or `-.1e5` may look like options. Under the default settings, such options are forbidden, and therefore those tokens are always recognized as non-options. However, if the `allow_ambiguous_opts` general setting is `true`, existing options in the argument table will take precedence: for example, if the option `-1` is added, and it takes an argument, then `-123` will be parsed as that option, and `23` will be its argument. Some ambiguities still remains though, because the `ArgParse` module can actually accept and parse expressions, not only numbers (although this is not the default), and therefore one may try to pass arguments like `-e` or `-pi`; in that case, these will always be at risk of being recognized as options. The easiest workaround is to put them in parentheses, e.g. `(-e)`. When an option is declared to accept a fixed positive number of arguments or the remainder of the command line (i.e. if `nargs` is a non-zero number, or `'A'`, or `'R'`), `parse_args` will not try to check if the argument(s) looks like an option. If `nargs` is one of `'?'` or `'*'` or `'+'`, then `parse_args` will take in only arguments which do not look like options. When `nargs` is `'+'` or `'*'` and an option is being parsed, then using the `'='` character will mark what follows as an argument (i.e. not an option); all which follows goes under the rules explained above. The same is true when short option groups are being parsed. For example, if the option in question is `-x`, then both `-y -x=-2 4 -y` and `-yx-2 4 -y` will parse `"-2"` and `"4"` as the arguments of `-x`. Finally, note that with the `eval_arg` setting expressions are evaluated during parsing, which means that there is no safeguard against passing things like ```run(`rm -rf someimportantthing`)``` and seeing your data evaporate (don't try that!). Be careful and generally try to avoid using the `eval_arg` setting. ================================================ FILE: docs/src/import.md ================================================ # Importing settings It may be useful in some cases to import an argument table into the one which is to be used, for example to create specialized versions of a common interface. ```@docs import_settings! ``` ================================================ FILE: docs/src/index.md ================================================ # ArgParse.jl documentation ```@meta CurrentModule = ArgParse ``` This [Julia](http://julialang.org) package allows the creation of user-friendly command-line interfaces to Julia programs: the program defines which arguments, options and sub-commands it accepts, and the `ArgParse` module does the actual parsing, issues errors when the input is invalid, and automatically generates help and usage messages. Users familiar with Python's `argparse` module will find many similarities, but some important differences as well. ## Installation To install the module, use Julia's package manager: start pkg mode by pressing `]` and then enter: ``` pkg> add ArgParse ``` Dependencies will be installed automatically. ## Quick overview and a simple example First of all, the module needs to be loaded: ```julia using ArgParse ``` There are two main steps for defining a command-line interface: creating an [`ArgParseSettings`](@ref) object, and populating it with allowed arguments and options using either the macro [`@add_arg_table!`](@ref) or the function [`add_arg_table!`](@ref) (see the [Argument table](@ref) section): ```julia s = ArgParseSettings() @add_arg_table! s begin "--opt1" help = "an option with an argument" "--opt2", "-o" help = "another option with an argument" arg_type = Int default = 0 "--flag1" help = "an option without argument, i.e. a flag" action = :store_true "arg1" help = "a positional argument" required = true end ``` In the macro, options and positional arguments are specified within a `begin...end` block, by one or more names in a line, optionally followed by a list of settings. So, in the above example, there are three options: * the first one, `"--opt1"` takes an argument, but doesn't check for its type, and it doesn't have a default value * the second one can be invoked in two different forms (`"--opt2"` and `"-o"`); it also takes an argument, but it must be of `Int` type (or convertible to it) and its default value is `0` * the third one, `--flag1`, is a flag, i.e. it doesn't take any argument. There is also only one positional argument, `"arg1"`, which is declared as mandatory. When the settings are in place, the actual argument parsing is performed via the [`parse_args`](@ref) function: ```julia parsed_args = parse_args(ARGS, s) ``` The parameter `ARGS` can be omitted. In case no errors are found, the result will be a `Dict{String,Any}` object. In the above example, it will contain the keys `"opt1"`, `"opt2"`, `"flag1"` and `"arg1"`, so that e.g. `parsed_args["arg1"]` will yield the value associated with the positional argument. (The `parse_args` function also accepts an optional `as_symbols` keyword argument: when set to `true`, the result of the parsing will be a `Dict{Symbol,Any}`, which can be useful e.g. for passing it as the keywords to a Julia function.) Putting all this together in a file, we can see how a basic command-line interface is created: ```julia using ArgParse function parse_commandline() s = ArgParseSettings() @add_arg_table! s begin "--opt1" help = "an option with an argument" "--opt2", "-o" help = "another option with an argument" arg_type = Int default = 0 "--flag1" help = "an option without argument, i.e. a flag" action = :store_true "arg1" help = "a positional argument" required = true end return parse_args(s) end function main() parsed_args = parse_commandline() println("Parsed args:") for (arg,val) in parsed_args println(" $arg => $val") end end main() ``` If we save this as a file called `myprog1.jl`, we can see how a `--help` option is added by default, and a help message is automatically generated and formatted: ```text $ julia myprog1.jl --help usage: myprog1.jl [--opt1 OPT1] [-o OPT2] [--flag1] [-h] arg1 positional arguments: arg1 a positional argument optional arguments: --opt1 OPT1 an option with an argument -o, --opt2 OPT2 another option with an argument (type: Int64, default: 0) --flag1 an option without argument, i.e. a flag -h, --help show this help message and exit ``` Also, we can see how invoking it with the wrong arguments produces errors: ```text $ julia myprog1.jl required argument arg1 was not provided usage: myprog1.jl [--opt1 OPT1] [-o OPT2] [--flag1] [-h] arg1 $ julia myprog1.jl somearg anotherarg too many arguments usage: myprog1.jl [--opt1 OPT1] [-o OPT2] [--flag1] [-h] arg1 $ julia myprog1.jl --opt2 1.5 somearg invalid argument: 1.5 (conversion to type Int64 failed; you may need to overload ArgParse.parse_item; the error was: ArgumentError("invalid base 10 digit '.' in \"1.5\"")) usage: myprog1.jl [--opt1 OPT1] [-o OPT2] [--flag1] arg1 ``` When everything goes fine instead, our program will print the resulting `Dict`: ```text $ julia myprog1.jl somearg Parsed args: arg1 => somearg opt2 => 0 opt1 => nothing flag1 => false $ julia myprog1.jl --opt1 "2+2" --opt2 "4" somearg --flag Parsed args: arg1 => somearg opt2 => 4 opt1 => 2+2 flag1 => true ``` From these examples, a number of things can be noticed: * `opt1` defaults to `nothing`, since no `default` setting was used for it in `@add_arg_table!` * `opt1` argument type, begin unspecified, defaults to `Any`, but in practice it's parsed as a string (e.g. `"2+2"`) * `opt2` instead has `Int` argument type, so `"4"` will be parsed and converted to an integer, an error is emitted if the conversion fails * positional arguments can be passed in between options * long options can be passed in abbreviated form (e.g. `--flag` instead of `--flag1`) as long as there's no ambiguity More examples can be found in the `examples` directory, and the complete documentation in the manual pages. ## Contents ```@contents Pages = [ "parse_args.md", "settings.md", "arg_table.md", "import.md", "conflicts.md", "custom.md", "details.md" ] ``` ================================================ FILE: docs/src/parse_args.md ================================================ # The `parse_args` function ```@docs parse_args ``` ================================================ FILE: docs/src/settings.md ================================================ # Settings ```@docs ArgParseSettings @project_version ``` ================================================ FILE: examples/argparse_example1.jl ================================================ # example 1: minimal options/arguments, auto-generated help/version using ArgParse function main(args) # initialize the settings (the description is for the help screen) s = ArgParseSettings(description = "Example 1 for argparse.jl: minimal usage.") @add_arg_table! s begin "--opt1" # an option (will take an argument) "--opt2", "-o" # another option, with short form "arg1" # a positional argument end parsed_args = parse_args(s) # the result is a Dict{String,Any} println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: examples/argparse_example2.jl ================================================ # example 2: add some flags and the help lines for options using ArgParse function main(args) s = ArgParseSettings("Example 2 for argparse.jl: " * # description "flags, options help, " * "required arguments.") @add_arg_table! s begin "--opt1" help = "an option" # used by the help screen "--opt2", "-o" action = :store_true # this makes it a flag help = "a flag" "arg1" help = "an argument" required = true # makes the argument mandatory end parsed_args = parse_args(args, s) println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: examples/argparse_example3.jl ================================================ # example 3: version information, default values, options with # types and variable number of arguments using ArgParse function main(args) s = ArgParseSettings("Example 3 for argparse.jl: " * "version info, default values, " * "options with types, variable " * "number of arguments.", version = "Version 1.0", # version info add_version = true) # audo-add version option @add_arg_table! s begin "--opt1" nargs = '?' # '?' means optional argument arg_type = Int # only Int arguments allowed default = 0 # this is used when the option is not passed constant = 1 # this is used if --opt1 is paseed with no argument help = "an option" "--karma", "-k" action = :count_invocations # increase a counter each time the option is given help = "increase karma" "arg1" nargs = 2 # eats up two arguments; puts the result in a Vector help = "first argument, two " * "entries at once" required = true "arg2" nargs = '*' # eats up as many arguments as possible before an option default = Any["no_arg_given"] # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) help = "second argument, eats up " * "as many items as possible " * "before an option" end parsed_args = parse_args(args, s) println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: examples/argparse_example4.jl ================================================ # example 4: dest_name, metavar, range_tester, alternative # actions, epilog with examples using ArgParse function main(args) s = ArgParseSettings("Example 4 for argparse.jl: " * "more tweaking of the arg fields: " * "dest_name, metvar, range_tested, " * "alternative actions.") @add_arg_table! s begin "--opt1" action = :append_const # appends 'constant' to 'dest_name' arg_type = String # the only utility of this is restricting the dest array type constant = "O1" dest_name = "O_stack" # this changes the destination help = "append O1" "--opt2" action = :append_const arg_type = String constant = "O2" dest_name = "O_stack" # same dest_name as opt1, different constant help = "append O2" "-k" action = :store_const # stores constant if given, default otherwise default = 0 constant = 42 help = "provide the answer" "--awkward-option" nargs = '+' # eats up as many argument as found (at least 1) action = :append_arg # argument chunks are appended when the option is # called repeatedly arg_type = String dest_name = "awk" range_tester = (x->x=="X"||x=="Y") # each argument must be either "X" or "Y" metavar = "XY" help = "either X or Y; all XY's are " * "stored in chunks" end # we add an epilog and provide usage examples, also demonstrating # how to have some control on the formatting: we use additional '\n' at # the end of lines to force newlines, and '\ua0' to put non-breakable spaces. # Non-breakable spaces are not removed and are not used to split lines; they # will be substituted with spaces in the final output. s.epilog = """ examples:\n \n \ua0\ua0$(basename(Base.source_path())) --opt1 --opt2 --opt2 -k\n \n \ua0\ua0$(basename(Base.source_path())) --awkward X X --opt1 --awkward X Y X --opt2\n \n The first form takes option 1 once, than option 2, then activates the answer flag, while the second form takes only option 1 and then 2, and intersperses them with "X\ua0X" and "X\ua0Y\ua0X" groups, for no particular reason. """ # the epilog section will be displayed like this in the help screen: # # examples: # # argparse_example4.jl --opt1 --opt2 --opt2 -kkkkk # # argparse_example4.jl --awkward X X --opt1 --awkward X Y X --opt2 # # The first form takes option 1 once, than option 2, then activates the # answer flag, while the second form takes only option 1 and then 2, and # intersperses them with "X X" and "X Y X" groups, for no particular # reason. parsed_args = parse_args(args, s) println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: examples/argparse_example5.jl ================================================ # example 5: manual help/version, import another parser using ArgParse function main(args) s0 = ArgParseSettings() # a "parent" structure e.g. one with some useful set of rules # which we want to extend # So we just add a simple table @add_arg_table! s0 begin "--parent-flag", "-o" action=>"store_true" help=>"parent flag" "--flag" action=>"store_true" help=>"another parent flag" "parent-argument" help = "parent argument" end s = ArgParseSettings("Example 5 for argparse.jl: " * "importing another parser, " * "manual help and version.", add_help = false, # disable auto-add of --help option version = "Version 1.0") # we set the version info, but --version won't be added import_settings!(s, s0) # now s has all of s0 arguments (except help/version) s.error_on_conflict = false # do not error-out when trying to override an option @add_arg_table! s begin "-o" # this will partially override s0's --parent-flag action = :store_true help = "child flag" "--flag" # this will fully override s0's --flag action = :store_true help = "another child flag" "-?", "--HELP", "--¡ḧëļṕ" # (almost) all characters allowed action = :show_help # will invoke the help generator help = "this will help you" "-v", "--VERSION" action = :show_version # will show version information help = "show version information" * "and exit" end parsed_args = parse_args(args, s) println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: examples/argparse_example6.jl ================================================ # example 6: commands & subtables using ArgParse function main(args) s = ArgParseSettings("Example 6 for argparse.jl: " * "commands and their associated sub-tables.") @add_arg_table! s begin "run" action = :command # adds a command which will be read from an argument help = "start running mode" "jump", "ju", "J" # another one, this one has two aliases action = :command help = "start jumping mode" end @add_arg_table! s["run"] begin # add command arg_table: same as usual, but invoked on s["cmd"] "--speed" arg_type = Float64 default = 10. help = "running speed, in Å/month" end s["jump"].description = "Jump mode for example 6" # this is how settings are tweaked # for commands s["jump"].commands_are_required = false # this makes the sub-commands optional s["jump"].autofix_names = true # this uses dashes in long options, underscores # in auto-generated dest_names @add_arg_table! s["jump"] begin "--higher" action = :store_true help = "enhance jumping" "--somersault" action = :command # this adds a sub-command (passed via an option instead) dest_name = "som" # flag commands can set a dest_name help = "somersault jumping mode" "--clap-feet" # dest_name will be "clap_feet" (see the "autofix_names" settings") action = :command help = "clap feet jumping mode" end s["jump"]["som"].description = "Somersault jump " * # this is how settings are tweaked "mode for example 6" # for sub-commands s["jump"]["clap_feet"].description = "Clap-feet jump " * # notice the underscore in the name "mode for example 6" parsed_args = parse_args(args, s) println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end println() # parsed_args will have a special field "%COMMAND%" # which will hold the executed command name (or 'nothing') println("Command: ", parsed_args["%COMMAND%"]) # thus, the command args are in parsed_args[parsed_args["%COMMAND%]] println("Parsed command args:") command_args = parsed_args[parsed_args["%COMMAND%"]] for (key,val) in command_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: examples/argparse_example7.jl ================================================ # example 7: argument groups using ArgParse function main(args) s = ArgParseSettings("Example 7 for argparse.jl: " * "argument groups.") add_arg_group!(s, "stack options") # add a group and sets it as the default @add_arg_table! s begin # all options (and arguments) in this table # will be assigned to the newly added group "--opt1" action = :append_const arg_type = String constant = "O1" dest_name = "O_stack" help = "append O1 to the stack" "--opt2" action = :append_const arg_type = String constant = "O2" dest_name = "O_stack" help = "append O2 to the stack" end add_arg_group!(s, "weird options", "weird") # another group, this time with a tag which allows # to refer to it set_default_arg_group!(s, "weird") # set the default group (useless here, since we just added it) @add_arg_table! s begin "--awkward-option" nargs = '+' action = :append_arg dest_name = "awk" arg_type = String range_tester = (x->x=="X"||x=="Y") metavar = "XY" help = "either X or Y; all XY's are " * "stored in chunks" end set_default_arg_group!(s) # reset the default arg group (which means arguments # are automatically assigned to commands/options/pos.args # groups) @add_arg_table! s begin "-k" action = :store_const default = 0 constant = 42 help = "provide the answer" "--şİłłÿ" action = :store_true help = "an option with a silly name" group = "weird" # this overrides the default group: this option # will be grouped together with --awkward-option end parsed_args = parse_args(args, s) println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: examples/argparse_example8.jl ================================================ # example 8: mutually exculsive and required groups using ArgParse function main(args) s = ArgParseSettings("Example 8 for argparse.jl: " * "mutually exclusive and requiredd groups.") add_arg_group!(s, "Mutually exclusive options", exclusive=true) @add_arg_table! s begin "--maybe", "-M" action = :store_true help = "maybe..." "--maybe-not", "-N" action = :store_true help = "maybe not..." end add_arg_group!(s, "Required mutually exclusive options", exclusive=true, required=true) @add_arg_table! s begin "--either", "-E" action = :store_true help = "choose the `either` option" "--or", "-O" action = :store_arg arg_type = Int help = "set the `or` option" end add_arg_group!(s, "Required arguments", required=true) @add_arg_table! s begin "--enhance", "-+" action = :store_const default = 0 constant = 42 help = "set the enhancement option" "arg1" nargs = 2 # eats up two arguments; puts the result in a Vector help = "first argument, two " * "entries at once" end parsed_args = parse_args(args, s) println("Parsed args:") for (key,val) in parsed_args println(" $key => $(repr(val))") end end main(ARGS) ================================================ FILE: src/ArgParse.jl ================================================ """ ArgParse This module allows the creation of user-friendly command-line interfaces to Julia programs: the program defines which arguments, options and sub-commands it accepts, and the `ArgParse` module does the actual parsing, issues errors when the input is invalid, and automatically generates help and usage messages. Users familiar with Python's `argparse` module will find many similarities, but some important differences as well. """ module ArgParse import TextWrap if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@compiler_options")) @eval Base.Experimental.@compiler_options compile=min optimize=0 infer=false end export # types ArgParseSettings, ArgParseSettingsError, ArgParseError, # functions & macros add_arg_table!, @add_arg_table!, add_arg_group!, set_default_arg_group!, import_settings!, usage_string, parse_args, @project_version import Base: show, getindex, setindex!, haskey @nospecialize # use only declared type signatures, helps with compile time include("common.jl") include("settings.jl") include("parsing.jl") include("deprecated.jl") end # module ArgParse ================================================ FILE: src/common.jl ================================================ ## Some common functions, constants, macros # auxiliary functions/constants found_a_bug() = error("you just found a bug in the ArgParse module, please report it.") const nbspc = '\u00a0' const nbsps = "$nbspc" println_unnbsp(io::IO, args...) = println(io, map(s->replace(s, nbspc => ' '), args)...) macro defaults(opts, ex) @assert ex.head == :block lines = filter(x->!(x isa LineNumberNode), ex.args) @assert all(x->Meta.isexpr(x, :(=)), lines) opts = esc(opts) # Transform the opts array into a Dict exret = :($opts = Dict{Symbol,Any}($opts)) # Initialize the checks found = esc(gensym("found")) exret = quote $exret $found = Dict{Symbol,Bool}(k => false for (k,v) in $opts) end for y in lines sym = y.args[1] qsym = Expr(:quote, sym) exret = quote $exret if haskey($opts, $qsym) $(esc(sym)) = $opts[$qsym] $found[$qsym] = true else $(esc(y)) end end end exret = quote $exret for (k,v) in $found v || serror("unknown description field: $k") end end exret end ================================================ FILE: src/deprecated.jl ================================================ @deprecate add_arg_table add_arg_table! @deprecate import_settings(settings, other) import_settings!(settings, other) @deprecate import_settings(settings, other, ao) import_settings!(settings, other; args_only=ao) @deprecate add_arg_group(args...; kw...) add_arg_group!(args...; kw...) @deprecate set_default_arg_group set_default_arg_group! # The Base.@deprecate macro doesn't work with macros # Here's an attempt at mimicking most of what that does # and deprecate @add_arg_table -> @add_arg_table! using Base: JLOptions, CoreLogging using Logging: @logmsg function callframe(st, name) found = false for sf in st sf == StackTraces.UNKNOWN && continue if found && sf.func == Symbol("top-level scope") return sf end sf.func == name && (found = true) end return StackTraces.UNKNOWN end export @add_arg_table macro add_arg_table(s, x...) opts = JLOptions() msg = "`@add_arg_table` is deprecated, use `@add_arg_table!` instead" opts.depwarn == 2 && throw(ErrorException(msg)) deplevel = opts.depwarn == 1 ? CoreLogging.Warn : CoreLogging.BelowMinLevel st = stacktrace() caller = callframe(st, Symbol("@add_arg_table")) @logmsg(deplevel, msg, _file = String(caller.file), _line = caller.line, _group = :depwarn, maxlog = 1) return _add_arg_table!(s, x...) end ================================================ FILE: src/parsing.jl ================================================ ## All types, functions and constants related to the actual process of ## parsing the arguments # ArgParseError struct ArgParseError <: Exception text::AbstractString end argparse_error(x...) = throw(ArgParseError(string(x...))) # parsing checks function test_range(range_tester::Function, arg, name::AbstractString) local rng_chk::Bool try rng_chk = range_tester(arg) catch rng_chk = false end rng_chk || argparse_error("out of range input for $name: $arg") return end function test_exclusive_groups!(exc_groups::Dict{ArgParseGroup,AbstractString}, settings::ArgParseSettings, f::ArgParseField, name::AbstractString) arg_group = get_group(f.group, f, settings) if haskey(exc_groups, arg_group) prev_id = exc_groups[arg_group] if isempty(prev_id) exc_groups[arg_group] = idstring(f) elseif prev_id != idstring(f) argparse_error("option $name not allowed with $prev_id") end end return end function test_required_args(settings::ArgParseSettings, found_args::Set{AbstractString}) req_groups = Dict{ArgParseGroup,Bool}(g=>false for g in settings.args_groups if g.required) fields = settings.args_table.fields for f in fields found = idstring(f) ∈ found_args !is_cmd(f) && f.required && !found && argparse_error("required $(idstring(f)) was not provided") found && (req_groups[get_group(f.group, f, settings)] = true) end for (g,found) in req_groups found && continue ids = String[idstring(f) for f in fields if get_group(f.group, f, settings) ≡ g] argparse_error("one of these is required: " * join(ids, ", ")) end return true end function check_settings_can_use_symbols(settings::ArgParseSettings) args_table = settings.args_table if !isempty(args_table.subsettings) for f in args_table.fields if f.dest_name == string(scmd_dest_name) serror("the dest_name $scmd_dest_name cannot be used with the as_symbols option") end end for subs in values(args_table.subsettings) check_settings_can_use_symbols(subs) end end settings.suppress_warnings && return true for f in args_table.fields if '-' in f.dest_name @warn "dest_name=$(f.dest_name) contains a hyphen; use the autofix_names=true setting to have it converted to an underscore" end end return true end # parsing aux functions function parse_item_wrapper(::Type{T}, x::AbstractString) where {T} local r::T try r = parse_item(T, x) catch err argparse_error(""" invalid argument: $x (conversion to type $T failed; you may need to overload ArgParse.parse_item; the error was: $err)""") end return r end parse_item(::Type{Any}, x::AbstractString) = x parse_item(::Type{T}, x::AbstractString) where {T<:Number} = parse(T, x) parse_item(::Type{T}, x::AbstractString) where {T} = applicable(convert, T, x) ? convert(T, x) : T(x) function parse_item_eval(::Type{T}, x::AbstractString) where {T} local r::T try r = convert(T, eval(Meta.parse(x))) catch err argparse_error(""" invalid argument: $x (must evaluate or convert to type $T; the error was: $err)""") end return r end const number_regex = r"^[+-]? # optional sign ( 0x[0-9a-fA-F](_?[0-9a-fA-F])* | # hex 0o[0-7](_?[0-7])* | # oct 0b[01](_?[01])* | # bin ( # float mantissa [0-9](_?[0-9])*(\.([0-9](_?[0-9])*)?)? | # start with digit \.[0-9](_?[0-9])* # start with dot )([eEf][-+]?[0-9]+)? # float optional exp ) $"x function looks_like_an_option(arg::AbstractString, settings::ArgParseSettings) arg == "-" && return false startswith(arg, "--") && return true startswith(arg, '-') || return false # begins with '-' # check if it's a number: occursin(number_regex, arg) || return true # looks like a number; but is it overridden by an option? d = arg[2:2] for a in settings.args_table.fields, s in a.short_opt_name s == d && return true end # it's a number return false end function usage_string(settings::ArgParseSettings) isempty(settings.usage) || return settings.usage usage_pre = "usage: " * (isempty(settings.prog) ? "" : settings.prog) lc_len_limit = settings.help_alignment_width cmd_lst = String[] pos_lst = String[] opt_lst = String[] exc_lst = Dict{String,Tuple{Bool,Vector{String}}}() for f in settings.args_table.fields arg_group = get_group(f.group, f, settings) if arg_group.exclusive (is_cmd(f) || is_arg(f)) && found_a_bug() _, tgt_opt_lst = get!(exc_lst, arg_group.name, (arg_group.required, String[])) else tgt_opt_lst = opt_lst end if is_cmd(f) if !isempty(f.short_opt_name) idstr = "-" * f.short_opt_name[1] elseif !isempty(f.long_opt_name) idstr = "--" * f.long_opt_name[1] else idstr = f.metavar end push!(cmd_lst, idstr) elseif is_arg(f) bra_pre, bra_post = f.required ? ("","") : ("[","]") if isa(f.nargs.desc, Int) if f.metavar isa AbstractString arg_str = join(repeat([f.metavar], f.nargs.desc), nbsps) else found_a_bug() end elseif f.nargs.desc == :A arg_str = f.metavar elseif f.nargs.desc == :? found_a_bug() elseif f.nargs.desc == :* || f.nargs.desc == :R || f.nargs.desc == :+ arg_str = f.metavar * "..." else found_a_bug() end push!(pos_lst, bra_pre * arg_str * bra_post) else bra_pre, bra_post = (f.required || arg_group.exclusive) ? ("","") : ("[","]") if !isempty(f.short_opt_name) opt_str1 = "-" * f.short_opt_name[1] else opt_str1 = "--" * f.long_opt_name[1] end if is_flag(f) opt_str2 = "" else if f.nargs.desc isa Int if f.metavar isa AbstractString opt_str2 = string(ntuple(i->(nbsps * f.metavar), f.nargs.desc)...) elseif f.metavar isa Vector opt_str2 = string(ntuple(i->(nbsps * f.metavar[i]), f.nargs.desc)...) else found_a_bug() end elseif f.nargs.desc == :A opt_str2 = nbsps * f.metavar elseif f.nargs.desc == :? opt_str2 = nbsps * "[" * f.metavar * "]" elseif f.nargs.desc == :* || f.nargs.desc == :R opt_str2 = nbsps * "[" * f.metavar * "...]" elseif f.nargs.desc == :+ opt_str2 = nbsps * f.metavar * nbsps * "[" * f.metavar * "...]" else found_a_bug() end end new_opt = bra_pre * opt_str1 * opt_str2 * bra_post push!(tgt_opt_lst, new_opt) end end excl_str = "" for (req,lst) in values(exc_lst) pre, post = req ? ("{","}") : ("[","]") excl_str *= " " * pre * join(lst, " | ") * post end if isempty(opt_lst) optl_str = "" else optl_str = " " * join(opt_lst, " ") end if isempty(pos_lst) posl_str = "" else posl_str = " " * join(pos_lst, " ") end if isempty(cmd_lst) cmdl_str = "" else bra_pre, bra_post = settings.commands_are_required ? ("{","}") : ("[","]") cmdl_str = " " * bra_pre * join(cmd_lst, "|") * bra_post end usage_len = length(usage_pre) + 1 str_nonwrapped = usage_pre * excl_str * optl_str * posl_str * cmdl_str str_wrapped = TextWrap.wrap(str_nonwrapped, break_long_words = false, break_on_hyphens = false, subsequent_indent = min(usage_len, lc_len_limit), width = settings.help_width) out_str = replace(str_wrapped, nbspc => ' ') return out_str end function string_compact(x...) io = IOBuffer() show(IOContext(io, :compact=>true), x...) return String(take!(io)) end function gen_help_text(arg::ArgParseField, settings::ArgParseSettings) is_flag(arg) && return arg.help pre = isempty(arg.help) ? "" : " " type_str = "" default_str = "" const_str = "" alias_str = "" if !is_command_action(arg.action) if arg.arg_type ≠ Any && !(arg.arg_type <: AbstractString) type_str = pre * "(type: " * string_compact(arg.arg_type) end if arg.default ≢ nothing && !isequal(arg.default, []) mid = isempty(type_str) ? " (" : ", " default_str = mid * "default: " * string_compact(arg.default) end if arg.nargs.desc == :? mid = isempty(type_str) && isempty(default_str) ? " (" : ", " const_str = mid * "without arg: " * string_compact(arg.constant) end else is_arg(arg) || found_a_bug() if !isempty(arg.cmd_aliases) alias_str = pre * "(aliases: " * join(arg.cmd_aliases, ", ") end end post = all(isempty, (type_str, default_str, const_str, alias_str)) ? "" : ")" return arg.help * type_str * default_str * const_str * alias_str * post end function print_group(io::IO, lst::Vector, desc::AbstractString, lc_usable_len::Int, lc_len::Int, lmargin::AbstractString, rmargin::AbstractString, sindent::AbstractString, width::Int) isempty(lst) && return println(io, desc, ":") for l in lst l1len = length(l[1]) if l1len ≤ lc_usable_len rfill = " "^(lc_len - l1len) ll_nonwrapped = l[1] * rfill * rmargin * l[2] ll_wrapped = TextWrap.wrap(ll_nonwrapped, break_long_words = false, break_on_hyphens = false, initial_indent = lmargin, subsequent_indent = sindent, width = width) println_unnbsp(io, ll_wrapped) else println_unnbsp(io, lmargin, l[1]) l2_wrapped = TextWrap.wrap(l[2], break_long_words = false, break_on_hyphens = false, initial_indent = sindent, subsequent_indent = sindent, width = width) println_unnbsp(io, l2_wrapped) end end println(io) end show_help(settings::ArgParseSettings; kw...) = show_help(stdout, settings; kw...) function show_help(io::IO, settings::ArgParseSettings; exit_when_done = !isinteractive()) lc_len_limit = settings.help_alignment_width lc_left_indent = 2 lc_right_margin = 2 lc_usable_len = lc_len_limit - lc_left_indent - lc_right_margin max_lc_len = 0 usage_str = usage_string(settings) group_lists = Dict{AbstractString,Vector{Any}}() for ag in settings.args_groups group_lists[ag.name] = Any[] end for f in settings.args_table.fields dest_lst = group_lists[f.group] if is_arg(f) push!(dest_lst, Any[f.metavar, gen_help_text(f, settings)]) max_lc_len = max(max_lc_len, length(f.metavar)) else opt_str1 = join([["-"*x for x in f.short_opt_name]; ["--"*x for x in f.long_opt_name]], ", ") if is_flag(f) opt_str2 = "" else if f.nargs.desc isa Int if f.metavar isa AbstractString opt_str2 = string(ntuple(i->(nbsps * f.metavar), f.nargs.desc)...) elseif isa(f.metavar, Vector) opt_str2 = string(ntuple(i->(nbsps * f.metavar[i]), f.nargs.desc)...) else found_a_bug() end elseif f.nargs.desc == :A opt_str2 = nbsps * f.metavar elseif f.nargs.desc == :? opt_str2 = nbsps * "[" * f.metavar * "]" elseif f.nargs.desc == :* || f.nargs.desc == :R opt_str2 = nbsps * "[" * f.metavar * "...]" elseif f.nargs.desc == :+ opt_str2 = nbsps * f.metavar * nbsps * "[" * f.metavar * "...]" else found_a_bug() end end new_opt = Any[opt_str1 * opt_str2, gen_help_text(f, settings)] push!(dest_lst, new_opt) max_lc_len = max(max_lc_len, length(new_opt[1])) end end lc_len = min(lc_usable_len, max_lc_len) lmargin = " "^lc_left_indent rmargin = " "^lc_right_margin sindent = lmargin * " "^lc_len * rmargin println(io, usage_str) println(io) show_message(io, settings.description, settings.preformatted_description, settings.help_width) for ag in settings.args_groups print_group(io, group_lists[ag.name], ag.desc, lc_usable_len, lc_len, lmargin, rmargin, sindent, settings.help_width) end show_message(io, settings.epilog, settings.preformatted_epilog, settings.help_width) exit_when_done && exit(0) return end function show_message(io::IO, message::AbstractString, preformatted::Bool, width::Int) if !isempty(message) if preformatted print(io, message) else for l in split(message, "\n\n") message_wrapped = TextWrap.wrap(l, break_long_words = false, break_on_hyphens = false, width = width) println_unnbsp(io, message_wrapped) end end println(io) end end show_version(settings::ArgParseSettings; kw...) = show_version(stdout, settings; kw...) function show_version(io::IO, settings::ArgParseSettings; exit_when_done = !isinteractive()) println(io, settings.version) exit_when_done && exit(0) return end has_cmd(settings::ArgParseSettings) = any(is_cmd, settings.args_table.fields) # parse_args & friends function default_handler(settings::ArgParseSettings, err, err_code::Int = 1) isinteractive() ? debug_handler(settings, err) : cmdline_handler(settings, err, err_code) end function cmdline_handler(settings::ArgParseSettings, err, err_code::Int = 1) println(stderr, err.text) println(stderr, usage_string(settings)) exit(err_code) end function debug_handler(settings::ArgParseSettings, err) rethrow(err) end parse_args(settings::ArgParseSettings; kw...) = parse_args(ARGS, settings; kw...) """ parse_args([args,] settings; as_symbols::Bool = false) This is the central function of the `ArgParse` module. It takes a `Vector` of arguments and an [`ArgParseSettings`](@ref) object, and returns a `Dict{String,Any}`. If `args` is not provided, the global variable `ARGS` will be used. When the keyword argument `as_symbols` is `true`, the function will return a `Dict{Symbol,Any}` instead. The returned `Dict` keys are defined (possibly implicitly) in `settings`, and their associated values are parsed from `args`. Special keys are used for more advanced purposes; at the moment, one such key exists: `%COMMAND%` (`_COMMAND_` when using `as_symbols=true`; see the [Commands](@ref) section). Arguments are parsed in sequence and matched against the argument table in `settings` to determine whether they are long options, short options, option arguments or positional arguments: * long options begin with a double dash `"--"`; if a `'='` character is found, the remainder is the option argument; therefore, `["--opt=arg"]` and `["--opt", "arg"]` are equivalent if `--opt` takes at least one argument. Long options can be abbreviated (e.g. `--opt` instead of `--option`) as long as there is no ambiguity. * short options begin with a single dash `"-"` and their name consists of a single character; they can be grouped together (e.g. `["-x", "-y"]` can become `["-xy"]`), but in that case only the last option in the group can take an argument (which can also be grouped, e.g. `["-a", "-f", "file.txt"]` can be passed as `["-affile.txt"]` if `-a` does not take an argument and `-f` does). The `'='` character can be used to separate option names from option arguments as well (e.g. `-af=file.txt`). * positional arguments are anything else; they can appear anywhere. The special string `"--"` can be used to signal the end of all options; after that, everything is considered as a positional argument (e.g. if `args = ["--opt1", "--", "--opt2"]`, the parser will recognize `--opt1` as a long option without argument, and `--opt2` as a positional argument). The special string `"-"` is always parsed as a positional argument. The parsing can stop early if a `:show_help` or `:show_version` action is triggered, or if a parsing error is found. Some ambiguities can arise in parsing, see the [Parsing details](@ref) section for a detailed description of how they're solved. """ function parse_args(args_list::Vector, settings::ArgParseSettings; as_symbols::Bool = false) as_symbols && check_settings_can_use_symbols(settings) local parsed_args try parsed_args = parse_args_unhandled(args_list, settings) catch err err isa ArgParseError || rethrow() settings.exc_handler(settings, err) end as_symbols && (parsed_args = convert_to_symbols(parsed_args)) return parsed_args end mutable struct ParserState args_list::Vector arg_delim_found::Bool token::Union{AbstractString,Nothing} token_arg::Union{AbstractString,Nothing} arg_consumed::Bool last_arg::Int found_args::Set{AbstractString} command::Union{AbstractString,Nothing} truncated_shopts::Bool abort::Bool exc_groups::Dict{ArgParseGroup,AbstractString} out_dict::Dict{String,Any} function ParserState(args_list::Vector, settings::ArgParseSettings, truncated_shopts::Bool) exc_groups = Dict{ArgParseGroup,AbstractString}( g=>"" for g in settings.args_groups if g.exclusive) out_dict = Dict{String,Any}() for f in settings.args_table.fields f.action ∈ (:show_help, :show_version) && continue out_dict[f.dest_name] = deepcopy(f.default) end return new(deepcopy(args_list), false, nothing, nothing, false, 0, Set{AbstractString}(), nothing, truncated_shopts, false, exc_groups, out_dict) end end found_command(state::ParserState) = state.command ≢ nothing function parse_command_args!(state::ParserState, settings::ArgParseSettings) cmd = state.command haskey(settings, cmd) || argparse_error("unknown command: $cmd") #state.out_dict[cmd] = parse_args(state.args_list, settings[cmd]) try state.out_dict[cmd] = parse_args_unhandled(state.args_list, settings[cmd], state.truncated_shopts) catch err err isa ArgParseError || rethrow(err) settings[cmd].exc_handler(settings[cmd], err) finally state.truncated_shopts = false end return state.out_dict[cmd] end function preparse!(c::Channel, state::ParserState, settings::ArgParseSettings) args_list = state.args_list while !isempty(args_list) state.arg_delim_found && (put!(c, :pos_arg); continue) arg = args_list[1] if state.truncated_shopts @assert arg[1] == '-' looks_like_an_option(arg, settings) || argparse_error("illegal short options sequence after command: $arg") state.truncated_shopts = false end if arg == "--" state.arg_delim_found = true state.token = nothing state.token_arg = nothing popfirst!(args_list) continue elseif startswith(arg, "--") eq = findfirst(isequal('='), arg) if eq ≢ nothing opt_name = arg[3:prevind(arg,eq)] arg_after_eq = arg[nextind(arg,eq):end] else opt_name = arg[3:end] arg_after_eq = nothing end isempty(opt_name) && argparse_error("illegal option: $arg") popfirst!(args_list) state.token = opt_name state.token_arg = arg_after_eq put!(c, :long_option) elseif looks_like_an_option(arg, settings) shopts_lst = arg[2:end] popfirst!(args_list) state.token = shopts_lst state.token_arg = nothing put!(c, :short_option_list) else state.token = nothing state.token_arg = nothing put!(c, :pos_arg) end end end # faithful reproduction of Python 3.5.1 argparse.py # partially Copyright © 2001-2016 Python Software Foundation; All Rights Reserved function read_args_from_files(arg_strings, prefixes) new_arg_strings = AbstractString[] for arg_string in arg_strings if isempty(arg_string) || arg_string[1] ∉ prefixes # for regular arguments, just add them back into the list push!(new_arg_strings, arg_string) else # replace arguments referencing files with the file content open(arg_string[nextind(arg_string, 1):end]) do args_file arg_strings = AbstractString[] for arg_line in readlines(args_file) push!(arg_strings, rstrip(arg_line, '\n')) end arg_strings = read_args_from_files(arg_strings, prefixes) append!(new_arg_strings, arg_strings) end end end # return the modified argument list return new_arg_strings end function parse_args_unhandled(args_list::Vector, settings::ArgParseSettings, truncated_shopts::Bool=false) all(x->(x isa AbstractString), args_list) || error("malformed args_list") if !isempty(settings.fromfile_prefix_chars) args_list = read_args_from_files(args_list, settings.fromfile_prefix_chars) end version_added = false help_added = false if settings.add_version settings.add_version = false add_arg_field!(settings, "--version", action = :show_version, help = "show version information and exit", group = "" ) version_added = true end if settings.add_help settings.add_help = false add_arg_field!(settings, ["--help", "-h"], action = :show_help, help = "show this help message and exit", group = "" ) help_added = true end state = ParserState(args_list, settings, truncated_shopts) preparser = Channel(c->preparse!(c, state, settings)) try for tag in preparser if tag == :long_option parse_long_opt!(state, settings) elseif tag == :short_option_list parse_short_opt!(state, settings) elseif tag == :pos_arg parse_arg!(state, settings) else found_a_bug() end state.abort && return nothing found_command(state) && break end test_required_args(settings, state.found_args) if found_command(state) cmd_dict = parse_command_args!(state, settings) cmd_dict ≡ nothing && return nothing elseif settings.commands_are_required && has_cmd(settings) argparse_error("no command given") end catch err rethrow() finally if help_added pop!(settings.args_table.fields) settings.add_help = true end if version_added pop!(settings.args_table.fields) settings.add_version = true end end return state.out_dict end # common parse functions function parse1_flag!(state::ParserState, settings::ArgParseSettings, f::ArgParseField, has_arg::Bool, opt_name::AbstractString) has_arg && argparse_error("option $opt_name takes no arguments") test_exclusive_groups!(state.exc_groups, settings, f, opt_name) command = nothing out_dict = state.out_dict if f.action == :store_true out_dict[f.dest_name] = true elseif f.action == :store_false out_dict[f.dest_name] = false elseif f.action == :store_const out_dict[f.dest_name] = f.constant elseif f.action == :append_const push!(out_dict[f.dest_name], f.constant) elseif f.action == :count_invocations out_dict[f.dest_name] += 1 elseif f.action == :command_flag out_dict[f.dest_name] = f.constant command = f.constant elseif f.action == :show_help show_help(settings, exit_when_done = settings.exit_after_help) state.abort = true elseif f.action == :show_version show_version(settings, exit_when_done = settings.exit_after_help) state.abort = true end state.command = command return end function parse1_optarg!(state::ParserState, settings::ArgParseSettings, f::ArgParseField, rest, name::AbstractString) args_list = state.args_list arg_delim_found = state.arg_delim_found out_dict = state.out_dict test_exclusive_groups!(state.exc_groups, settings, f, name) arg_consumed = false parse_function = f.eval_arg ? parse_item_eval : parse_item_wrapper command = nothing is_multi_nargs(f.nargs) && (opt_arg = Array{f.arg_type}(undef, 0)) if f.nargs.desc isa Int num::Int = f.nargs.desc num > 0 || found_a_bug() corr = (rest ≡ nothing) ? 0 : 1 if length(args_list) + corr < num argparse_error("$name requires $num argument", num > 1 ? "s" : "") end if rest ≢ nothing a = parse_function(f.arg_type, rest) test_range(f.range_tester, a, name) push!(opt_arg, a) arg_consumed = true end for i = (1+corr):num a = parse_function(f.arg_type, popfirst!(args_list)) test_range(f.range_tester, a, name) push!(opt_arg, a) end elseif f.nargs.desc == :A if rest ≢ nothing a = parse_function(f.arg_type, rest) test_range(f.range_tester, a, name) opt_arg = a arg_consumed = true else isempty(args_list) && argparse_error("option $name requires an argument") a = parse_function(f.arg_type, popfirst!(args_list)) test_range(f.range_tester, a, name) opt_arg = a end elseif f.nargs.desc == :? if rest ≢ nothing a = parse_function(f.arg_type, rest) test_range(f.range_tester, a, name) opt_arg = a arg_consumed = true else if isempty(args_list) || looks_like_an_option(args_list[1], settings) opt_arg = deepcopy(f.constant) else a = parse_function(f.arg_type, popfirst!(args_list)) test_range(f.range_tester, a, name) opt_arg = a end end elseif f.nargs.desc == :* || f.nargs.desc == :+ arg_found = false if rest ≢ nothing a = parse_function(f.arg_type, rest) test_range(f.range_tester, a, name) push!(opt_arg, a) arg_consumed = true arg_found = true end while !isempty(args_list) if !arg_delim_found && looks_like_an_option(args_list[1], settings) break end a = parse_function(f.arg_type, popfirst!(args_list)) test_range(f.range_tester, a, name) push!(opt_arg, a) arg_found = true end if f.nargs.desc == :+ && !arg_found argparse_error("option $name requires at least one not-option-looking argument") end elseif f.nargs.desc == :R if rest ≢ nothing a = parse_function(f.arg_type, rest) test_range(f.range_tester, a, name) push!(opt_arg, a) arg_consumed = true end while !isempty(args_list) a = parse_function(f.arg_type, popfirst!(args_list)) test_range(f.range_tester, a, name) push!(opt_arg, a) end else found_a_bug() end if f.action == :store_arg out_dict[f.dest_name] = opt_arg elseif f.action == :append_arg push!(out_dict[f.dest_name], opt_arg) elseif f.action == :command_arg if !haskey(settings, opt_arg) found = false for f1 in settings.args_table.fields (is_cmd(f1) && is_arg(f1)) || continue for al in f1.cmd_aliases if opt_arg == al found = true opt_arg = f1.constant break end end found && break end !found && argparse_error("unknown command: $opt_arg") haskey(settings, opt_arg) || found_a_bug() end out_dict[f.dest_name] = opt_arg command = opt_arg else found_a_bug() end state.arg_consumed = arg_consumed state.command = command return end # parse long opts function parse_long_opt!(state::ParserState, settings::ArgParseSettings) opt_name = state.token arg_after_eq = state.token_arg local f::ArgParseField local fln::AbstractString exact_match = false nfound = 0 for g in settings.args_table.fields for ln in g.long_opt_name if ln == opt_name exact_match = true nfound = 1 f = g fln = ln break elseif startswith(ln, opt_name) nfound += 1 f = g fln = ln end end exact_match && break end nfound == 0 && argparse_error("unrecognized option --$opt_name") nfound > 1 && argparse_error("long option --$opt_name is ambiguous ($nfound partial matches)") opt_name = fln if is_flag(f) parse1_flag!(state, settings, f, arg_after_eq ≢ nothing, "--"*opt_name) else parse1_optarg!(state, settings, f, arg_after_eq, "--"*opt_name) end push!(state.found_args, idstring(f)) return end # parse short opts function parse_short_opt!(state::ParserState, settings::ArgParseSettings) shopts_lst = state.token rest_as_arg = nothing sind = firstindex(shopts_lst) while sind ≤ ncodeunits(shopts_lst) opt_char, next_sind = iterate(shopts_lst, sind) if next_sind ≤ ncodeunits(shopts_lst) next_opt_char, next2_sind = iterate(shopts_lst, next_sind) if next_opt_char == '=' next_is_eq = true rest_as_arg = shopts_lst[next2_sind:end] else next_is_eq = false rest_as_arg = shopts_lst[next_sind:end] end else next_is_eq = false rest_as_arg = nothing end opt_name = string(opt_char) local f::ArgParseField found = false for outer f in settings.args_table.fields found |= any(sn->sn==opt_name, f.short_opt_name) found && break end found || argparse_error("unrecognized option -$opt_name") if is_flag(f) parse1_flag!(state, settings, f, next_is_eq, "-"*opt_name) else parse1_optarg!(state, settings, f, rest_as_arg, "-"*opt_name) end push!(state.found_args, idstring(f)) state.arg_consumed && break if found_command(state) if rest_as_arg ≢ nothing && !isempty(rest_as_arg) startswith(rest_as_arg, '-') && argparse_error("illegal short options sequence after command " * "$(state.command): $rest_as_arg") pushfirst!(state.args_list, "-" * rest_as_arg) state.truncated_shopts = true end return end sind = next_sind end end # parse arg function parse_arg!(state::ParserState, settings::ArgParseSettings) found = false local f::ArgParseField for new_arg_ind = state.last_arg+1:length(settings.args_table.fields) f = settings.args_table.fields[new_arg_ind] if is_arg(f) && !f.fake found = true state.last_arg = new_arg_ind break end end found || argparse_error("too many arguments") parse1_optarg!(state, settings, f, nothing, f.dest_name) push!(state.found_args, idstring(f)) return end # convert_to_symbols convert_to_symbols(::Nothing) = nothing function convert_to_symbols(parsed_args::Dict{String,Any}) new_parsed_args = Dict{Symbol,Any}() cmd = nothing if haskey(parsed_args, cmd_dest_name) cmd = parsed_args[cmd_dest_name] if cmd ≡ nothing scmd = nothing else scmd = Symbol(cmd) new_parsed_args[scmd] = convert_to_symbols(parsed_args[cmd]) end new_parsed_args[scmd_dest_name] = scmd end for (k,v) in parsed_args (k == cmd_dest_name || k == cmd) && continue new_parsed_args[Symbol(k)] = v end return new_parsed_args end ================================================ FILE: src/settings.jl ================================================ ## All types, functions and constants related to the specification of the arguments # actions const all_actions = [:store_arg, :store_true, :store_false, :store_const, :append_arg, :append_const, :count_invocations, :command, :show_help, :show_version] const internal_actions = [:store_arg, :store_true, :store_false, :store_const, :append_arg, :append_const, :count_invocations, :command_arg, :command_flag, :show_help, :show_version] const nonflag_actions = [:store_arg, :append_arg, :command_arg] is_flag_action(a::Symbol) = a ∉ nonflag_actions const multi_actions = [:append_arg, :append_const] is_multi_action(a::Symbol) = a ∈ multi_actions const command_actions = [:command_arg, :command_flag] is_command_action(a::Symbol) = a ∈ command_actions # ArgParseSettingsError struct ArgParseSettingsError <: Exception text::AbstractString end serror(x...) = throw(ArgParseSettingsError(string(x...))) # ArgConsumerType struct ArgConsumerType desc::Union{Int,Symbol} function ArgConsumerType(n::Integer) n ≥ 0 || serror("nargs can't be negative") new(n) end function ArgConsumerType(s::Symbol) s ∈ [:A, :?, :*, :+, :R] || serror("nargs must be an integer or one of 'A', '?', '*', '+', 'R'") new(s) end end ArgConsumerType(c::Char) = ArgConsumerType(Symbol(c)) ArgConsumerType() = ArgConsumerType(:A) function show(io::IO, nargs::ArgConsumerType) print(io, nargs.desc isa Int ? nargs.desc : "'" * string(nargs.desc) * "'") end is_multi_nargs(nargs::ArgConsumerType) = nargs.desc ∉ (0, :A, :?) default_action(nargs::Integer) = nargs == 0 ? :store_true : :store_arg default_action(nargs::Char) = :store_arg default_action(nargs::Symbol) = :store_arg default_action(nargs::ArgConsumerType) = default_action(nargs.desc) # ArgParseGroup mutable struct ArgParseGroup name::AbstractString desc::AbstractString exclusive::Bool required::Bool function ArgParseGroup(name::AbstractString, desc::AbstractString, exclusive::Bool = false, required::Bool = false ) new(name, desc, exclusive, required) end end const cmd_group = ArgParseGroup("commands", "commands") const pos_group = ArgParseGroup("positional", "positional arguments") const opt_group = ArgParseGroup("optional", "optional arguments") const std_groups = [cmd_group, pos_group, opt_group] # ArgParseField mutable struct ArgParseField dest_name::AbstractString long_opt_name::Vector{AbstractString} short_opt_name::Vector{AbstractString} arg_type::Type action::Symbol nargs::ArgConsumerType default constant range_tester::Function required::Bool eval_arg::Bool help::AbstractString metavar::Union{AbstractString,Vector{<:AbstractString}} cmd_aliases::Vector{AbstractString} group::AbstractString fake::Bool ArgParseField() = new("", AbstractString[], AbstractString[], Any, :store_true, ArgConsumerType(), nothing, nothing, _->true, false, false, "", "", AbstractString[], "", false) end is_flag(arg::ArgParseField) = is_flag_action(arg.action) is_arg(arg::ArgParseField) = isempty(arg.long_opt_name) && isempty(arg.short_opt_name) is_cmd(arg::ArgParseField) = is_command_action(arg.action) const cmd_dest_name = "%COMMAND%" const scmd_dest_name = :_COMMAND_ function show(io::IO, s::ArgParseField) println(io, "ArgParseField(") for f in fieldnames(ArgParseField) println(io, " ", f, "=", getfield(s, f)) end print(io, " )") end # ArgParseTable mutable struct ArgParseTable fields::Vector{ArgParseField} subsettings::Dict{AbstractString,Any} # will actually be a Dict{AbstractString,ArgParseSettings} ArgParseTable() = new(ArgParseField[], Dict{AbstractString,Any}()) end # disallow alphanumeric, - function check_prefix_chars(chars) result = Set{Char}() for c in chars if isletter(c) || isnumeric(c) || c == '-' throw(ArgParseError("‘$(c)’ is not allowed as prefix character")) end push!(result, c) end result end # ArgParseSettings """ ArgParseSettings The `ArgParseSettings` object contains all the settings to be used during argument parsing. Settings are divided in two groups: general settings and argument-table-related settings. While the argument table requires specialized functions such as [`@add_arg_table!`](@ref) to be defined and manipulated, general settings are simply object fields (most of them are `Bool` or `String`) and can be passed to the constructor as keyword arguments, or directly set at any time. This is the list of general settings currently available: * `prog` (default = `""`): the name of the program, as displayed in the auto-generated help and usage screens. If left empty, the source file name will be used. * `description` (default = `""`): a description of what the program does, to be displayed in the auto-generated help-screen, between the usage lines and the arguments description. If `preformatted_description` is `false` (see below), it will be automatically formatted, but you can still force newlines by using two consecutive newlines in the string, and manually control spaces by using non-breakable spaces (the character `'\\ua0'`). * `preformatted_description` (default = `false`): disable automatic formatting of `description`. * `epilog` (default = `""`): like `description`, but will be displayed at the end of the help-screen, after the arguments description. The same formatting rules also apply. * `preformatted_epilog` (default = `false`): disable automatic formatting of `epilog`. * `usage` (default = `""`): the usage line(s) to be displayed in the help screen and when an error is found during parsing. If left empty, it will be auto-generated. * `version` (default = `"Unknown version"`): version information. It's used by the `:show_version` action. * `add_help` (default = `true`): if `true`, a `--help, -h` option (triggering the `:show_help` action) is added to the argument table. * `add_version` (default = `false`): if `true`, a `--version` option (triggering the `:show_version` action) is added to the argument table. * `help_width` (default = `70`): set the width of the help text. This does not affect the `usage` line if it is provided by the user rather than being auto-generated; it also does not affect the `description/epilog` lines if the `preformatted_description/epilog` options are set to `false`. * `help_alignment_width` (default = `24`): set the maximum width of the left column of the help text, where options and arguments names are listed. This also affects the indentation of the usage line when it is auto-generated. It must be ≥ 4 and should be less than `help_width`. * `fromfile_prefix_chars` (default = `Set{Char}()`): an argument beginning with one of these characters will specify a file from which arguments will be read, one argument read per line. Alphanumeric characters and the hyphen-minus (`'-'`) are prohibited. * `autofix_names` (default = `false`): if `true`, will try to automatically fix the uses of dashes (`'-'`) and underscores (`'_'`) in option names and destinations: all underscores will be converted to dashes in long option names; also, associated destination names, if auto-generated (see the [Argument names](@ref) section), will have dashes replaced with underscores, both for long options and for positional arguments. For example, an option declared as `"--my-opt"` will be associated with the key `"my_opt"` by default. It is especially advisable to turn this option on then parsing with the `as_symbols=true` argument to `parse_args`. * `error_on_conflict` (default = `true`): if `true`, throw an error in case conflicting entries are added to the argument table; if `false`, later entries will silently take precedence. See the [Conflicts and overrides](@ref) srction for a detailed description of what conflicts are and what is the exact behavior when this setting is `false`. * `suppress_warnings` (default = `false`): if `true`, all warnings will be suppressed. * `allow_ambiguous_opts` (default = `false`): if `true`, ambiguous options such as `-1` will be accepted. * `commands_are_required` (default = `true`): if `true`, commands will be mandatory. See the [Commands](@ref) section. * `exc_handler` (default = `ArgParse.default_handler`): this is a function which is invoked when an error is detected during parsing (e.g. an option is not recognized, a required argument is not passed etc.). It takes two arguments: the `settings::ArgParseSettings` object and the `err::ArgParseError` exception. The default handler behaves differently depending on whether it's invoked from a script or in an interactive environment (e.g. REPL/IJulia). In non-interactive (script) mode, it calls `ArgParse.cmdline_handler`, which prints the error text and the usage screen on standard error and exits Julia with error code 1: ```julia function cmdline_handler(settings::ArgParseSettings, err, err_code::Int = 1) println(stderr, err.text) println(stderr, usage_string(settings)) exit(err_code) end ``` In interactive mode instead it calls the function `ArgParse.debug_handler`, which just rethrows the error. * `exit_after_help` (default = `!isinteractive()`): exit Julia (with error code `0`) when the `:show_help` or `:show_version` actions are triggered. If `false`, those actions will just stop the parsing and make `parse_args` return `nothing`. Here is a usage example: ```julia settings = ArgParseSettings(description = "This program does something", commands_are_required = false, version = "1.0", add_version = true) ``` which is also equivalent to: ```julia settings = ArgParseSettings() settings.description = "This program does something." settings.commands_are_required = false settings.version = "1.0" settings.add_version = true ``` As a shorthand, the `description` field can be passed without keyword, which makes this equivalent to the above: ```julia settings = ArgParseSettings("This program does something", commands_are_required = false, version = "1.0", add_version = true) ``` Most settings won't take effect until `parse_args` is invoked, but a few will have immediate effects: `autofix_names`, `error_on_conflict`, `suppress_warnings`, `allow_ambiguous_opts`. """ mutable struct ArgParseSettings prog::AbstractString description::AbstractString epilog::AbstractString usage::AbstractString version::AbstractString add_help::Bool add_version::Bool help_width::Int help_alignment_width::Int fromfile_prefix_chars::Set{Char} autofix_names::Bool error_on_conflict::Bool suppress_warnings::Bool allow_ambiguous_opts::Bool commands_are_required::Bool args_groups::Vector{ArgParseGroup} default_group::AbstractString args_table::ArgParseTable exc_handler::Function preformatted_description::Bool preformatted_epilog::Bool exit_after_help::Bool function ArgParseSettings(;prog::AbstractString = Base.source_path() ≢ nothing ? basename(Base.source_path()) : "", description::AbstractString = "", epilog::AbstractString = "", usage::AbstractString = "", version::AbstractString = "Unspecified version", add_help::Bool = true, add_version::Bool = false, help_width::Integer = 70, help_alignment_width::Integer = 24, fromfile_prefix_chars = Set{Char}(), autofix_names::Bool = false, error_on_conflict::Bool = true, suppress_warnings::Bool = false, allow_ambiguous_opts::Bool = false, commands_are_required::Bool = true, exc_handler::Function = default_handler, preformatted_description::Bool = false, preformatted_epilog::Bool = false, exit_after_help::Bool = !isinteractive() ) fromfile_prefix_chars = check_prefix_chars(fromfile_prefix_chars) return new( prog, description, epilog, usage, version, add_help, add_version, help_width, help_alignment_width, fromfile_prefix_chars, autofix_names, error_on_conflict, suppress_warnings, allow_ambiguous_opts, commands_are_required, copy(std_groups), "", ArgParseTable(), exc_handler, preformatted_description, preformatted_epilog, exit_after_help ) end end ArgParseSettings(desc::AbstractString; kw...) = ArgParseSettings(; description = desc, kw...) function show(io::IO, s::ArgParseSettings) println(io, "ArgParseSettings(") for f in fieldnames(ArgParseSettings) f ∈ (:args_groups, :args_table) && continue println(io, " ", f, "=", getfield(s, f)) end println(io, " >> ", usage_string(s)) print(io, " )") end ArgName{T<:AbstractString} = Union{T, Vector{T}} getindex(s::ArgParseSettings, c::AbstractString) = s.args_table.subsettings[c] haskey(s::ArgParseSettings, c::AbstractString) = haskey(s.args_table.subsettings, c) setindex!(s::ArgParseSettings, x::ArgParseSettings, c::AbstractString) = setindex!(s.args_table.subsettings, x, c) # fields declarations sanity checks function check_name_format(name::ArgName) isempty(name) && serror("empty name") name isa Vector || return true allopts = true allargs = true for n in name isempty(n) && serror("empty name") if startswith(n, '-') allargs = false else allopts = false end end !(allargs || allopts) && serror("multiple names must be either all options or all non-options") for i1 = 1:length(name), i2 = i1+1:length(name) name[i1] == name[i2] && serror("duplicate name $(name[i1])") end return true end function check_type(opt, T::Type, message::AbstractString) opt isa T || serror(message) return true end function check_eltype(opt, T::Type, message::AbstractString) eltype(opt) <: T || serror(message) return true end function warn_extra_opts(opts, valid_keys::Vector{Symbol}) for k in opts k ∈ valid_keys || @warn "ignored option: $k" end return true end function check_action_is_valid(action::Symbol) action ∈ all_actions || serror("invalid action: $action") end function check_nargs_and_action(nargs::ArgConsumerType, action::Symbol) is_flag_action(action) && nargs.desc ≠ 0 && nargs.desc ≠ :A && serror("incompatible nargs and action (flag-action $action, nargs=$nargs)") is_command_action(action) && nargs.desc ≠ :A && serror("incompatible nargs and action (command action, nargs=$nargs)") !is_flag_action(action) && nargs.desc == 0 && serror("incompatible nargs and action (non-flag-action $action, nargs=$nargs)") return true end function check_long_opt_name(name::AbstractString, settings::ArgParseSettings) '=' ∈ name && serror("illegal option name: $name (contains '=')") occursin(r"\s", name) && serror("illegal option name: $name (contains whitespace)") settings.add_help && name == "help" && serror("option --help is reserved in the current settings") settings.add_version && name == "version" && serror("option --version is reserved in the current settings") return true end function check_short_opt_name(name::AbstractString, settings::ArgParseSettings) length(name) ≠ 1 && serror("short options must use a single character") name == "=" && serror("illegal short option name: $name") occursin(r"\s", name) && serror("illegal option name: $name (contains whitespace)") !settings.allow_ambiguous_opts && occursin(r"[0-9.(]", name) && serror("ambiguous option name: $name (disabled in current settings)") settings.add_help && name == "h" && serror("option -h is reserved for help in the current settings") return true end function check_arg_name(name::AbstractString) occursin(r"^%[A-Z]*%$", name) && serror("invalid positional arg name: $name (is reserved)") return true end function check_cmd_name(name::AbstractString) isempty(name) && found_a_bug() startswith(name, '-') && found_a_bug() occursin(r"\s", name) && serror("invalid command name: $name (contains whitespace)") occursin(r"^%[A-Z]*%$", name) && serror("invalid command name: $name (is reserved)") return true end function check_dest_name(name::AbstractString) occursin(r"^%[A-Z]*%$", name) && serror("invalid dest_name: $name (is reserved)") return true end function idstring(arg::ArgParseField) if is_arg(arg) return "argument $(arg.metavar)" elseif !isempty(arg.long_opt_name) return "option --$(arg.long_opt_name[1])" else return "option -$(arg.short_opt_name[1])" end end # TODO improve (test more nonsensical cases) function check_arg_makes_sense(settings::ArgParseSettings, arg::ArgParseField) is_arg(arg) || return true is_command_action(arg.action) && return true for f in settings.args_table.fields is_arg(f) || continue is_command_action(f.action) && serror("non-command $(idstring(arg)) can't follow commands") !f.required && arg.required && serror("required $(idstring(arg)) can't follow non-required arguments") end return true end function check_conflicts_with_commands(settings::ArgParseSettings, new_arg::ArgParseField, allow_future_merge::Bool) for cmd in keys(settings.args_table.subsettings) cmd == new_arg.dest_name && serror("$(idstring(new_arg)) has the same destination of a command: $cmd") end for a in settings.args_table.fields if is_cmd(a) && !is_cmd(new_arg) for l1 in a.long_opt_name, l2 in new_arg.long_opt_name # TODO be less strict here and below, and allow partial override? l1 == l2 && serror("long opt name --$(l1) already in use by command $(a.constant)") end for s1 in a.short_opt_name, s2 in new_arg.short_opt_name s1 == s2 && serror("short opt name -$(s1) already in use by command $(a.constant)") end elseif is_cmd(a) && is_cmd(new_arg) if a.constant == new_arg.constant allow_future_merge || serror("command $(a.constant) already in use") is_arg(a) ≠ is_arg(new_arg) && serror("$(idstring(a)) and $(idstring(new_arg)) are incompatible") else for al in new_arg.cmd_aliases al == a.constant && serror("invalid alias $al, command already in use") end end end end return true end function check_conflicts_with_commands(settings::ArgParseSettings, new_cmd::AbstractString) for a in settings.args_table.fields new_cmd == a.dest_name && serror("command $new_cmd has the same destination of $(idstring(a))") end return true end function check_for_duplicates(args::Vector{ArgParseField}, new_arg::ArgParseField) for a in args for l1 in a.long_opt_name, l2 in new_arg.long_opt_name l1 == l2 && serror("duplicate long opt name $l1") end for s1 in a.short_opt_name, s2 in new_arg.short_opt_name s1 == s2 && serror("duplicate short opt name $s1") end if is_arg(a) && is_arg(new_arg) && a.metavar == new_arg.metavar serror("two arguments have the same metavar: $(a.metavar)") end if is_cmd(a) && is_cmd(new_arg) for al1 in a.cmd_aliases, al2 in new_arg.cmd_aliases al1 == al2 && serror("both commands $(a.constant) and $(new_arg.constant) use the same alias $al1") end for al1 in a.cmd_aliases al1 == new_arg.constant && serror("$al1 already in use as an alias command $(a.constant)") end for al2 in new_arg.cmd_aliases al2 == a.constant && serror("invalid alias $al2, command already in use") end end if a.dest_name == new_arg.dest_name a.arg_type == new_arg.arg_type || serror("$(idstring(a)) and $(idstring(new_arg)) have the same destination but different arg types") if (is_multi_action(a.action) && !is_multi_action(new_arg.action)) || (!is_multi_action(a.action) && is_multi_action(new_arg.action)) serror("$(idstring(a)) and $(idstring(new_arg)) have the same destination but incompatible actions") end end end return true end function typecompatible(default::D, arg_type::Type) where D D <: arg_type && return true try applicable(convert, arg_type, default) ? convert(arg_type, default) : arg_type(default) return true catch end return false end check_default_type(default::Nothing, arg_type::Type) = true function check_default_type(default::D, arg_type::Type) where D typecompatible(default, arg_type) || serror("typeof(default)=$D is incompatible with arg_type=$arg_type)") return true end check_default_type_multi(default::Nothing, arg_type::Type) = true function check_default_type_multi(default::Vector, arg_type::Type) all(x->typecompatible(x, arg_type), default) || serror("all elements of the default value must be of type $arg_type or convertible to it") return true end check_default_type_multi(default::D, arg_type::Type) where D = serror("typeof(default)=$D is incompatible with nargs, it should be a Vector") check_default_type_multi2(default::Nothing, arg_type::Type) = true function check_default_type_multi2(default::Vector{D}, arg_type::Type) where D all(y->(y isa Vector), default) || serror("the default $(default) is incompatible with the action and nargs, it should be a Vector of Vectors") all(y->all(x->typecompatible(x, arg_type), y), default) || serror("all elements of the default value must be of type $arg_type or convertible to it") return true end check_default_type_multi2(default::D, arg_type::Type) where D = serror("the default $(default) is incompatible with the action and nargs, it should be a Vector of Vectors") function _convert_default(arg_type::Type, default::D) where D D <: arg_type && return default applicable(convert, arg_type, default) ? convert(arg_type, default) : arg_type(default) end convert_default(arg_type::Type, default::Nothing) = nothing convert_default(arg_type::Type, default) = _convert_default(arg_type, default) convert_default_multi(arg_type::Type, default::Nothing) = Array{arg_type}(undef, 0) convert_default_multi(arg_type::Type, default::Vector) = arg_type[_convert_default(arg_type, x) for x in default] convert_default_multi2(arg_type::Type, default::Nothing) = Array{Vector{arg_type}}(undef, 0) convert_default_multi2(arg_type::Type, default::Vector) = Vector{arg_type}[arg_type[_convert_default(arg_type, x) for x in y] for y in default] check_range_default(default::Nothing, range_tester::Function) = true function check_range_default(default, range_tester::Function) local res::Bool try res = range_tester(default) catch err serror("the range_tester function must be defined for the default value, and return a Bool") end res || serror("the default value must pass the range_tester function") return true end check_range_default_multi(default::Nothing, range_tester::Function) = true function check_range_default_multi(default::Vector, range_tester::Function) for d in default local res::Bool try res = range_tester(d) catch err serror("the range_tester function must be defined for all the default values, and return a Bool") end res || serror("all of the default values must pass the range_tester function") end return true end check_range_default_multi2(default::Nothing, range_tester::Function) = true function check_range_default_multi2(default::Vector, range_tester::Function) for dl in default, d in dl local res::Bool try res = range_tester(d) catch err serror("the range_tester function must be defined for all the default values, and return a Bool") end res || serror("all of the default values must pass the range_tester function") end return true end function check_metavar(metavar::AbstractString) isempty(metavar) && serror("empty metavar") startswith(metavar, '-') && serror("metavars cannot begin with -") occursin(r"\s", metavar) && serror("illegal metavar name: $metavar (contains whitespace)") return true end function check_metavar(metavar::Vector{<:AbstractString}) foreach(check_metavar, metavar) return true end function check_group_name(name::AbstractString) isempty(name) && serror("empty group name") startswith(name, '#') && serror("invalid group name (starts with #)") return true end # add_arg_table! and related function name_to_fieldnames!(settings::ArgParseSettings, name::ArgName) pos_arg = "" long_opts = AbstractString[] short_opts = AbstractString[] aliases = AbstractString[] r(n) = settings.autofix_names ? replace(n, '_' => '-') : n function do_one(n, cmd_check = true) if startswith(n, "--") n == "--" && serror("illegal option name: --") long_opt_name = r(n[3:end]) check_long_opt_name(long_opt_name, settings) push!(long_opts, long_opt_name) elseif startswith(n, '-') n == "-" && serror("illegal option name: -") short_opt_name = n[2:end] check_short_opt_name(short_opt_name, settings) push!(short_opts, short_opt_name) else if cmd_check check_cmd_name(n) else check_arg_name(n) end if isempty(pos_arg) pos_arg = n else push!(aliases, n) end end end if name isa Vector foreach(do_one, name) else do_one(name, false) end return pos_arg, long_opts, short_opts, aliases end function auto_dest_name(pos_arg::AbstractString, long_opts::Vector{AbstractString}, short_opts::Vector{AbstractString}, autofix_names::Bool) r(n) = autofix_names ? replace(n, '-' => '_') : n isempty(pos_arg) || return r(pos_arg) isempty(long_opts) || return r(long_opts[1]) @assert !isempty(short_opts) return short_opts[1] end function auto_metavar(dest_name::AbstractString, is_opt::Bool) is_opt || return dest_name prefix = occursin(r"^[[:alpha:]_]", dest_name) ? "" : "_" return prefix * uppercase(dest_name) end function get_cmd_prog_hint(arg::ArgParseField) isempty(arg.short_opt_name) || return "-" * arg.short_opt_name[1] isempty(arg.long_opt_name) || return "--" * arg.long_opt_name[1] return arg.constant end """ add_arg_table!(settings, [arg_name [,arg_options]]...) This function is very similar to the macro version [`@add_arg_table!`](@ref). Its syntax is stricter: tuples and blocks are not allowed and argument options are explicitly specified as `Dict` objects. However, since it doesn't involve macros, it offers more flexibility in other respects, e.g. the `arg_name` entries need not be explicit, they can be anything which evaluates to a `String` or a `Vector{String}`. Example: ```julia add_arg_table!(settings, ["--opt1", "-o"], Dict( :help => "an option with an argument" ), "--opt2", "arg1", Dict( :help => "a positional argument" :required => true )) ``` """ function add_arg_table!(settings::ArgParseSettings, table::Union{ArgName,Vector,Dict}...) has_name = false for i = 1:length(table) !has_name && !(table[i] isa ArgName) && serror("option field must be preceded by the arg name") has_name = true end i = 1 while i ≤ length(table) if i+1 ≤ length(table) && !(table[i+1] isa ArgName) add_arg_field!(settings, table[i]; table[i+1]...) i += 2 else add_arg_field!(settings, table[i]) i += 1 end end return settings end """ @add_arg_table!(settings, table...) This macro adds a table of arguments and options to the given `settings`. It can be invoked multiple times. The arguments groups are determined automatically, or the current default group is used if specified (see the [Argument groups](@ref) section for more details). The `table` is a list in which each element can be either `String`, or a tuple or a vector of `String`, or an assigmment expression, or a block: * a `String`, a tuple or a vector introduces a new positional argument or option. Tuples and vectors are only allowed for options or commands, and provide alternative names (e.g. `["--opt", "-o"]` or `["checkout", "co"]`) * assignment expressions (i.e. expressions using `=`, `:=` or `=>`) describe the previous argument behavior (e.g. `help = "an option"` or `required => false`). See the [Argument entry settings](@ref) section for a complete description * blocks (`begin...end` or lists of expressions in parentheses separated by semicolons) are useful to group entries and span multiple lines. These rules allow for a variety usage styles, which are discussed in the [Argument table styles](@ref) section. In the rest of the documentation, we will mostly use this style: ```julia @add_arg_table! settings begin "--opt1", "-o" help = "an option with an argument" "--opt2" "arg1" help = "a positional argument" required = true end ``` In the above example, the `table` is put in a single `begin...end` block and the line `"--opt1", "-o"` is parsed as a tuple; indentation is used to help readability. See also the function [`add_arg_table!`](@ref). """ macro add_arg_table!(s, x...) _add_arg_table!(s, x...) end # Moved all the code to a function just to make the deprecation work function _add_arg_table!(s, x...) # transform the tuple into a vector, so that # we can manipulate it x = Any[x...] # escape the ArgParseSettings s = esc(s) z = esc(gensym()) # start building the return expression exret = quote $z = $s $z isa ArgParseSettings || serror("first argument to @add_arg_table! must be of type ArgParseSettings") end # initialize the name and the options expression name = nothing exopt = Any[:Dict] # iterate over the arguments i = 1 while i ≤ length(x) y = x[i] if Meta.isexpr(y, :block) # found a begin..end block: expand its contents # in-place and restart from the same position splice!(x, i, y.args) continue elseif Meta.isexpr(y, :macrocall) && ((y.args[1] == GlobalRef(Core, Symbol("@doc"))) || (Meta.isexpr(y.args[1], :core) && y.args[1].args[1] == Symbol("@doc"))) # Was parsed as doc syntax. Split into components splice!(x, i, y.args[2:end]) continue elseif (y isa AbstractString) || Meta.isexpr(y, (:vect, :tuple)) Meta.isexpr(y, :tuple) && (y.head = :vect) # transform tuples into vectors if Meta.isexpr(y, :vect) && (isempty(y.args) || !all(x->x isa AbstractString, y.args)) # heterogeneous elements: splice it in place, just like blocks splice!(x, i, y.args) continue end # found a string, or a vector/tuple of strings: # this must be the option name if name ≢ nothing # there was a previous arg field on hold # first, concretely build the options opt = Expr(:call, exopt...) kopts = Expr(:parameters, Expr(:(...), opt)) # then, call add_arg_field! aaf = Expr(:call, :add_arg_field!, kopts, z, name) # store it in the output expression exret = quote $exret $aaf end end # put the name on hold, reinitialize the options expression name = y exopt = Any[:Dict] i += 1 elseif Meta.isexpr(y, (:(=), :(:=), :kw)) # found an assignment: add it to the current options expression name ≢ nothing || serror("malformed table: description fields must be preceded by the arg name") push!(exopt, Expr(:call, :(=>), Expr(:quote, y.args[1]), esc(y.args[2]))) i += 1 elseif Meta.isexpr(y, :call) && y.args[1] == :(=>) # found an assignment: add it to the current options expression name ≢ nothing || serror("malformed table: description fields must be preceded by the arg name") push!(exopt, Expr(:call, :(=>), Expr(:quote, y.args[2]), esc(y.args[3]))) i += 1 elseif (y isa LineNumberNode) || Meta.isexpr(y, :line) # a line number node, ignore i += 1 continue else # anything else: ignore, but issue a warning @warn "@add_arg_table!: ignoring expression $y" i += 1 end end if name ≢ nothing # there is an arg field on hold # same as above opt = Expr(:call, exopt...) kopts = Expr(:parameters, Expr(:(...), opt)) aaf = Expr(:call, :add_arg_field!, kopts, z, name) exret = quote $exret $aaf end end # the return value when invoking the macro # will be the ArgParseSettings object exret = quote $exret $z end # return the resulting expression exret end function get_group(group::AbstractString, arg::ArgParseField, settings::ArgParseSettings) if isempty(group) is_cmd(arg) && return cmd_group is_arg(arg) && return pos_group return opt_group else for ag in settings.args_groups group == ag.name && return ag end serror("group $group not found, use add_arg_group! to add it") end found_a_bug() end function add_arg_field!(settings::ArgParseSettings, name::ArgName; desc...) check_name_format(name) supplied_opts = keys(desc) @defaults desc begin nargs = ArgConsumerType() action = default_action(nargs) arg_type = Any default = nothing constant = nothing required = false range_tester = x->true eval_arg = false dest_name = "" help = "" metavar = "" force_override = !settings.error_on_conflict group = settings.default_group end check_type(nargs, Union{ArgConsumerType,Int,Char}, "nargs must be an Int or a Char") check_type(action, Union{AbstractString,Symbol}, "action must be an AbstractString or a Symbol") check_type(arg_type, Type, "invalid arg_type") check_type(required, Bool, "required must be a Bool") check_type(range_tester, Function, "range_tester must be a Function") check_type(dest_name, AbstractString, "dest_name must be an AbstractString") check_type(help, AbstractString, "help must be an AbstractString") # Check metavar's type to be either an AbstractString or a # Vector{T<:AbstractString} metavar_error = "metavar must be an AbstractString or a Vector{<:AbstractString}" if !(metavar isa AbstractString) check_type(metavar, Vector, metavar_error) check_eltype(metavar, AbstractString, metavar_error) check_type(nargs, Integer, "nargs must be an integer for multiple metavars") length(metavar) == nargs || serror("metavar array must have length of nargs") end check_type(force_override, Bool, "force_override must be a Bool") check_type(group, Union{AbstractString,Symbol}, "group must be an AbstractString or a Symbol") nargs isa ArgConsumerType || (nargs = ArgConsumerType(nargs)) action isa Symbol || (action = Symbol(action)) is_opt = name isa Vector ? startswith(first(name), '-') : startswith(name, '-') check_action_is_valid(action) action == :command && (action = is_opt ? :command_flag : :command_arg) check_nargs_and_action(nargs, action) new_arg = ArgParseField() is_flag = is_flag_action(action) if !is_opt is_flag && serror("invalid action for positional argument: $action") nargs.desc == :? && serror("invalid 'nargs' for positional argument: '?'") metavar isa Vector && serror("multiple metavars only supported for optional arguments") end pos_arg, long_opts, short_opts, cmd_aliases = name_to_fieldnames!(settings, name) if !isempty(cmd_aliases) is_command_action(action) || serror("only command arguments can have multiple names (aliases)") end new_arg.dest_name = auto_dest_name(pos_arg, long_opts, short_opts, settings.autofix_names) new_arg.long_opt_name = long_opts new_arg.short_opt_name = short_opts new_arg.cmd_aliases = cmd_aliases new_arg.nargs = nargs new_arg.action = action group = string(group) if :group ∈ supplied_opts && !isempty(group) check_group_name(group) end arg_group = get_group(group, new_arg, settings) new_arg.group = arg_group.name if arg_group.exclusive && (!is_opt || is_command_action(action)) serror("group $(new_arg.group) is mutually-exclusive, actions and commands are not allowed") end if action ∈ (:store_const, :append_const) && :constant ∉ supplied_opts serror("action $action requires the 'constant' field") end valid_keys = [:nargs, :action, :help, :force_override, :group] if is_flag if action ∈ (:store_const, :append_const) append!(valid_keys, [:default, :constant, :arg_type, :dest_name]) elseif action ∈ (:store_true, :store_false, :count_invocations, :command_flag) push!(valid_keys, :dest_name) else action ∈ (:show_help, :show_version) || found_a_bug() end elseif is_opt append!(valid_keys, [:arg_type, :default, :range_tester, :dest_name, :required, :metavar, :eval_arg]) nargs.desc == :? && push!(valid_keys, :constant) elseif action ≠ :command_arg append!(valid_keys, [:arg_type, :default, :range_tester, :required, :metavar]) end settings.suppress_warnings || warn_extra_opts(supplied_opts, valid_keys) if is_command_action(action) if (:dest_name ∈ supplied_opts) && (:dest_name ∈ valid_keys) cmd_name = dest_name else cmd_name = new_arg.dest_name end end if (:dest_name ∈ supplied_opts) && (:dest_name ∈ valid_keys) && (action ≠ :command_flag) new_arg.dest_name = dest_name end check_dest_name(dest_name) set_if_valid(k, x) = k ∈ valid_keys && setfield!(new_arg, k, x) set_if_valid(:arg_type, arg_type) set_if_valid(:default, deepcopy(default)) set_if_valid(:constant, deepcopy(constant)) set_if_valid(:range_tester, range_tester) set_if_valid(:required, required) set_if_valid(:help, help) set_if_valid(:metavar, metavar) set_if_valid(:eval_arg, eval_arg) if !is_flag isempty(new_arg.metavar) && (new_arg.metavar = auto_metavar(new_arg.dest_name, is_opt)) check_metavar(new_arg.metavar) end if is_command_action(action) new_arg.dest_name = cmd_dest_name new_arg.arg_type = AbstractString new_arg.constant = cmd_name new_arg.metavar = cmd_name cmd_prog_hint = get_cmd_prog_hint(new_arg) end if is_flag if action == :store_true new_arg.arg_type = Bool new_arg.default = false new_arg.constant = true elseif action == :store_false new_arg.arg_type = Bool new_arg.default = true new_arg.constant = false elseif action == :count_invocations new_arg.arg_type = Int new_arg.default = 0 elseif action == :store_const check_default_type(new_arg.default, new_arg.arg_type) check_default_type(new_arg.constant, new_arg.arg_type) new_arg.default = convert_default(new_arg.arg_type, new_arg.default) elseif action == :append_const check_default_type(new_arg.constant, new_arg.arg_type) if :arg_type ∉ supplied_opts new_arg.arg_type = typeof(new_arg.constant) end check_default_type_multi(new_arg.default, new_arg.arg_type) new_arg.default = convert_default_multi(new_arg.arg_type, new_arg.default) elseif action == :command_flag # nothing to do elseif action == :show_help || action == :show_version # nothing to do else found_a_bug() end else arg_type = new_arg.arg_type range_tester = new_arg.range_tester default = new_arg.default if !is_multi_action(new_arg.action) && !is_multi_nargs(new_arg.nargs) check_default_type(default, arg_type) check_range_default(default, range_tester) new_arg.default = convert_default(arg_type, default) elseif !is_multi_action(new_arg.action) || !is_multi_nargs(new_arg.nargs) check_default_type_multi(default, arg_type) check_range_default_multi(default, range_tester) new_arg.default = convert_default_multi(arg_type, default) else check_default_type_multi2(default, arg_type) check_range_default_multi2(default, range_tester) new_arg.default = convert_default_multi2(arg_type, default) end if is_opt && nargs.desc == :? constant = new_arg.constant check_default_type(constant, arg_type) check_range_default(constant, range_tester) new_arg.constant = convert_default(arg_type, constant) end end if action == :command_arg for f in settings.args_table.fields if f.action == :command_arg new_arg.fake = true break end end end check_arg_makes_sense(settings, new_arg) check_conflicts_with_commands(settings, new_arg, false) if force_override override_duplicates!(settings.args_table.fields, new_arg) else check_for_duplicates(settings.args_table.fields, new_arg) end push!(settings.args_table.fields, new_arg) is_command_action(action) && add_command!(settings, cmd_name, cmd_prog_hint, force_override) return end function add_command!(settings::ArgParseSettings, command::AbstractString, prog_hint::AbstractString, force_override::Bool) haskey(settings, command) && serror("command $command already added") if force_override override_conflicts_with_commands!(settings, command) else check_conflicts_with_commands(settings, command) end settings[command] = ArgParseSettings() ss = settings[command] ss.prog = "$(isempty(settings.prog) ? "" : settings.prog) $prog_hint" ss.description = "" ss.preformatted_description = settings.preformatted_description ss.epilog = "" ss.preformatted_epilog = settings.preformatted_epilog ss.usage = "" ss.version = settings.version ss.add_help = settings.add_help ss.add_version = settings.add_version ss.autofix_names = settings.autofix_names ss.fromfile_prefix_chars = settings.fromfile_prefix_chars ss.error_on_conflict = settings.error_on_conflict ss.suppress_warnings = settings.suppress_warnings ss.allow_ambiguous_opts = settings.allow_ambiguous_opts ss.exc_handler = settings.exc_handler ss.exit_after_help = settings.exit_after_help return ss end autogen_group_name(desc::AbstractString) = "#$(hash(desc))" add_arg_group!(settings::ArgParseSettings, desc::AbstractString; exclusive::Bool = false, required::Bool = false) = _add_arg_group!(settings, desc, autogen_group_name(desc), true, exclusive, required) """ add_arg_group!(settings, description, [name , [set_as_default]]; keywords...) This function adds an argument group to the argument table in `settings`. The `description` is a `String` used in the help screen as a title for that group. The `name` is a unique name which can be provided to refer to that group at a later time. Groups can be declared to be mutually exclusive and/or required, see below. After invoking this function, all subsequent invocations of the [`@add_arg_table!`](@ref) macro and [`add_arg_table!`](@ref) function will use the new group as the default, unless `set_as_default` is set to `false` (the default is `true`, and the option can only be set if providing a `name`). Therefore, the most obvious usage pattern is: for each group, add it and populate the argument table of that group. Example: ```julia-repl julia> settings = ArgParseSettings(); julia> add_arg_group!(settings, "custom group"); julia> @add_arg_table! settings begin "--opt" "arg" end; julia> parse_args(["--help"], settings) usage: [--opt OPT] [-h] [arg] optional arguments: -h, --help show this help message and exit custom group: --opt OPT arg ``` As seen from the example, new groups are always added at the end of existing ones. The `name` can also be passed as a `Symbol`. Forbidden names are the standard groups names (`"command"`, `"positional"` and `"optional"`) and those beginning with a hash character `'#'`. In order to declare a group as mutually exclusive, use the keyword `exclusive = true`. Mutually exclusive groups can only contain options, not arguments nor commands, and parsing will fail if more than one option from the group is provided. A group can be declared as required using the `required = true` keyword, in which case at least one option or positional argument or command from the group must be provided. """ function add_arg_group!(settings::ArgParseSettings, desc::AbstractString, tag::Union{AbstractString,Symbol}, set_as_default::Bool = true; exclusive::Bool = false, required::Bool = false ) name = string(tag) check_group_name(name) _add_arg_group!(settings, desc, name, set_as_default, exclusive, required) end function _add_arg_group!(settings::ArgParseSettings, desc::AbstractString, name::AbstractString, set_as_default::Bool, exclusive::Bool, required::Bool ) already_added = any(ag->ag.name==name, settings.args_groups) already_added || push!(settings.args_groups, ArgParseGroup(name, desc, exclusive, required)) set_as_default && (settings.default_group = name) return settings end """ set_default_arg_group!(settings, [name]) Set the default group for subsequent invocations of the [`@add_arg_table!`](@ref) macro and [`add_arg_table!`](@ref) function. `name` is a `String`, and must be one of the standard group names (`"command"`, `"positional"` or `"optional"`) or one of the user-defined names given in `add_arg_group!` (groups with no assigned name cannot be used with this function). If `name` is not provided or is the empty string `""`, then the default behavior is reset (i.e. arguments will be automatically assigned to the standard groups). The `name` can also be passed as a `Symbol`. """ function set_default_arg_group!(settings::ArgParseSettings, name::Union{AbstractString,Symbol} = "") name = string(name) startswith(name, '#') && serror("invalid group name: $name (begins with #)") isempty(name) && (settings.default_group = ""; return) found = any(ag->ag.name==name, settings.args_groups) found || serror("group $name not found") settings.default_group = name return end # import_settings! & friends function override_conflicts_with_commands!(settings::ArgParseSettings, new_cmd::AbstractString) ids0 = Int[] for ia in 1:length(settings.args_table.fields) a = settings.args_table.fields[ia] new_cmd == a.dest_name && push!(ids0, ia) end while !isempty(ids0) splice!(settings.args_table.fields, pop!(ids0)) end end function override_duplicates!(args::Vector{ArgParseField}, new_arg::ArgParseField) ids0 = Int[] for (ia,a) in enumerate(args) if (a.dest_name == new_arg.dest_name) && ((a.arg_type ≠ new_arg.arg_type) || (is_multi_action(a.action) && !is_multi_action(new_arg.action)) || (!is_multi_action(a.action) && is_multi_action(new_arg.action))) # unsolvable conflict, mark for deletion push!(ids0, ia) continue end if is_arg(a) && is_arg(new_arg) && !(is_cmd(a) && is_cmd(new_arg)) && a.metavar == new_arg.metavar # unsolvable conflict, mark for deletion push!(ids0, ia) continue end # delete conflicting command aliases for different commands if is_cmd(a) && is_cmd(new_arg) && a.constant ≠ new_arg.constant ids = Int[] for (ial1, al1) in enumerate(a.cmd_aliases) if al1 == new_arg.constant push!(ids, ial1) else for al2 in new_arg.cmd_aliases al1 == al2 && push!(ids, ial1) end end end while !isempty(ids) splice!(a.cmd_aliases, pop!(ids)) end end if is_arg(a) || is_arg(new_arg) # not an option, skip continue end if is_cmd(a) && is_cmd(new_arg) && a.constant == new_arg.constant && !is_arg(a) is_arg(new_arg) && found_a_bug() # this is ensured by check_settings_are_compatible # two command flags with the same command -> should have already been taken care of, # by either check_settings_are_compatible or merge_commands! continue end # delete conflicting long options ids = Int[] for il1 = 1:length(a.long_opt_name), l2 in new_arg.long_opt_name l1 = a.long_opt_name[il1] l1 == l2 && push!(ids, il1) end while !isempty(ids) splice!(a.long_opt_name, pop!(ids)) end # delete conflicting short options ids = Int[] for is1 in 1:length(a.short_opt_name), s2 in new_arg.short_opt_name s1 = a.short_opt_name[is1] s1 == s2 && push!(ids, is1) end while !isempty(ids) splice!(a.short_opt_name, pop!(ids)) end # if everything was deleted, remove the field altogether # (i.e. mark it for deletion) isempty(a.long_opt_name) && isempty(a.short_opt_name) && push!(ids0, ia) end # actually remove the marked fields while !isempty(ids0) splice!(args, pop!(ids0)) end end function check_settings_are_compatible(settings::ArgParseSettings, other::ArgParseSettings) table = settings.args_table otable = other.args_table for a in otable.fields check_conflicts_with_commands(settings, a, true) settings.error_on_conflict && check_for_duplicates(table.fields, a) end for (subk, subs) in otable.subsettings settings.error_on_conflict && check_conflicts_with_commands(settings, subk) haskey(settings, subk) && check_settings_are_compatible(settings[subk], subs) end return true end function merge_commands!(fields::Vector{ArgParseField}, ofields::Vector{ArgParseField}) oids = Int[] for a in fields, ioa = 1:length(ofields) oa = ofields[ioa] if is_cmd(a) && is_cmd(oa) && a.constant == oa.constant is_arg(a) ≠ is_arg(oa) && found_a_bug() # ensured by check_settings_are_compatible for l in oa.long_opt_name l ∈ a.long_opt_name || push!(a.long_opt_name, l) end for s in oa.short_opt_name s ∈ a.short_opt_name || push!(a.short_opt_name, s) end for al in oa.cmd_aliases al ∈ a.cmd_aliases || push!(a.cmd_aliases, al) end a.group = oa.group # note: the group may not be present yet, but it will be # added later push!(oids, ioa) end end # we return the merged ofields indices, since we still need to use them for overriding options # before we actually remove them return oids end function fix_commands_fields!(fields::Vector{ArgParseField}) cmd_found = false for a in fields if is_arg(a) && is_cmd(a) a.fake = cmd_found cmd_found = true end end end """ import_settings!(settings, other_settings [,args_only]) Imports `other_settings` into `settings`, where both are [`ArgParseSettings`](@ref) objects. If `args_only` is `true` (this is the default), only the argument table will be imported; otherwise, the default argument group will also be imported, and all general settings except `prog`, `description`, `epilog`, `usage` and `version`. Sub-settings associated with commands will also be imported recursively; the `args_only` setting applies to those as well. If there are common commands, their sub-settings will be merged. While importing, conflicts may arise: if `settings.error_on_conflict` is `true`, this will result in an error, otherwise conflicts will be resolved in favor of `other_settings` (see the [Conflicts and overrides](@ref) section for a detailed discussion of how conflicts are handled). Argument groups will also be imported; if two groups in `settings` and `other_settings` match, they are merged (groups match either by name, or, if unnamed, by their description). Note that the import will have effect immediately: any subsequent modification of `other_settings` will not have any effect on `settings`. This function can be used at any time. """ function import_settings!(settings::ArgParseSettings, other::ArgParseSettings; args_only::Bool = true) check_settings_are_compatible(settings, other) fields = settings.args_table.fields ofields = deepcopy(other.args_table.fields) merged_oids = merge_commands!(fields, ofields) if !settings.error_on_conflict for a in ofields override_duplicates!(fields, a) end for (subk, subs) in other.args_table.subsettings override_conflicts_with_commands!(settings, subk) end end while !isempty(merged_oids) splice!(ofields, pop!(merged_oids)) end append!(fields, ofields) for oag in other.args_groups skip = false for ag in settings.args_groups # TODO: merge groups in some cases if oag.name == ag.name skip = true break end end skip && continue push!(settings.args_groups, deepcopy(oag)) end fix_commands_fields!(fields) if !args_only settings.add_help = other.add_help settings.add_version = other.add_version settings.error_on_conflict = other.error_on_conflict settings.suppress_warnings = other.suppress_warnings settings.exc_handler = other.exc_handler settings.allow_ambiguous_opts = other.allow_ambiguous_opts settings.commands_are_required = other.commands_are_required settings.default_group = other.default_group settings.preformatted_description = other.preformatted_description settings.preformatted_epilog = other.preformatted_epilog settings.fromfile_prefix_chars = other.fromfile_prefix_chars settings.autofix_names = other.autofix_names settings.exit_after_help = other.exit_after_help end for (subk, subs) in other.args_table.subsettings cmd_prog_hint = "" for oa in other.args_table.fields if is_cmd(oa) && oa.constant == subk cmd_prog_hint = get_cmd_prog_hint(oa) break end end if !haskey(settings, subk) add_command!(settings, subk, cmd_prog_hint, !settings.error_on_conflict) elseif !isempty(cmd_prog_hint) settings[subk].prog = "$(settings.prog) $cmd_prog_hint" end import_settings!(settings[subk], subs, args_only=args_only) end return settings end """ @project_version @project_version(filename::String...) Reads the version from the Project.toml file at the given filename, at compile time. If no filename is given, defaults to `Base.current_project()`. If multiple strings are given, they will be joined with `joinpath`. Intended for use with the [`ArgParseSettings`](@ref) constructor, to keep the settings version in sync with the project version. ## Example ```julia ArgParseSettings(add_version = true, version = @project_version) ``` """ macro project_version(filename::Vararg{String}) project_version(isempty(filename) ? Base.current_project() : joinpath(filename...)) end function project_version(filename::AbstractString)::String re = r"^version\s*=\s*\"(.*)\"\s*$" for line in eachline(filename) if startswith(line, "[") break end if !occursin(re, line) continue end return match(re, line)[1] end throw(ArgumentError("Could not find a version in the file at $(filename)")) end ================================================ FILE: test/Project.toml ================================================ version = "1.0.0" [deps] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" ================================================ FILE: test/argparse_test01.jl ================================================ # test 01: minimal options/arguments, auto-generated help/version; # function version of add_arg_table! @testset "test 01" begin function ap_settings1() s = ArgParseSettings() @add_arg_table! s begin "--opt1" # an option (will take an argument) "--opt2", "-o" # another option, with short form "arg1" # a positional argument end s.exc_handler = ArgParse.debug_handler return s end function ap_settings1b() s = ArgParseSettings(exc_handler = ArgParse.debug_handler) add_arg_table!(s, "--opt1", ["--opt2", "-o"], "arg1") return s end for s = [ap_settings1(), ap_settings1b()] ap_test1(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--opt1 OPT1] [-o OPT2] [arg1] positional arguments: arg1 optional arguments: --opt1 OPT1 -o, --opt2 OPT2 """ @test ap_test1([]) == Dict{String,Any}("opt1"=>nothing, "opt2"=>nothing, "arg1"=>nothing) @test ap_test1(["arg"]) == Dict{String,Any}("opt1"=>nothing, "opt2"=>nothing, "arg1"=>"arg") @test ap_test1(["--opt1", "X", "-o=5", "--", "-arg"]) == Dict{String,Any}("opt1"=>"X", "opt2"=>"5", "arg1"=>"-arg") @test ap_test1(["--opt1", ""]) == Dict{String,Any}("opt1"=>"", "opt2"=>nothing, "arg1"=>nothing) @ap_test_throws ap_test1(["--opt1", "X", "-o=5", "-arg"]) @test ap_test1(["--opt1=", "--opt2=5"]) == Dict{String,Any}("opt1"=>"", "opt2"=>"5", "arg1"=>nothing) @test ap_test1(["-o", "-2"]) == Dict{String,Any}("opt1"=>nothing, "opt2"=>"-2", "arg1"=>nothing) @ap_test_throws ap_test1(["--opt", "3"]) # ambiguous @ap_test_throws ap_test1(["-o"]) @ap_test_throws ap_test1(["--opt1"]) @aps_test_throws @add_arg_table!(s, "--opt1") # long option already added @aps_test_throws @add_arg_table!(s, "-o") # short option already added end # test malformed tables function ap_settings1c() @aps_test_throws @add_arg_table! begin "-a" end s = ArgParseSettings(exc_handler = ArgParse.debug_handler) @aps_test_throws add_arg_table!(s, Dict(:action => :store_true), "-a") @test_addtable_failure s begin action = :store_true "-a" end @test_addtable_failure s begin action => :store_true end @aps_test_throws add_arg_table!(s, "-a", Dict(:wat => :store_true)) @aps_test_throws @add_arg_table! s begin "-a" wat => :store_true end end ap_settings1c() end ================================================ FILE: test/argparse_test02.jl ================================================ # test 02: version information, default values, flags, # options with types, optional arguments, variable # number of arguments; # function version of add_arg_table! @testset "test 02" begin function ap_settings2() s = ArgParseSettings(description = "Test 2 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "--opt1" nargs = '?' # '?' means optional argument arg_type = Int # only Int arguments allowed default = 0 # this is used when the option is not passed constant = 1 # this is used if --opt1 is paseed with no argument help = "an option" "-O" arg_type = Symbol default = :xyz help = "another option" "--flag", "-f" action = :store_true # this makes it a flag help = "a flag" "--karma", "-k" action = :count_invocations # increase a counter each time the option is given help = "increase karma" "arg1" nargs = 2 # eats up two arguments; puts the result in a Vector help = "first argument, two " * "entries at once" required = true "arg2" nargs = '*' # eats up as many arguments as possible before an option default = Any["no_arg_given"] # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) help = "second argument, eats up " * "as many items as possible " * "before an option" end return s end function ap_settings2b() s = ArgParseSettings(description = "Test 2 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) add_arg_table!(s, "--opt1", Dict( :nargs => '?', # '?' means optional argument :arg_type => Int, # only Int arguments allowed :default => 0, # this is used when the option is not passed :constant => 1, # this is used if --opt1 is paseed with no argument :help => "an option"), ["-O"], Dict( :arg_type => Symbol, :default => :xyz, :help => "another option"), ["--flag", "-f"], Dict( :action => :store_true, # this makes it a flag :help => "a flag"), ["--karma", "-k"], Dict( :action => :count_invocations, # increase a counter each time the option is given :help => "increase karma"), "arg1", Dict( :nargs => 2, # eats up two arguments; puts the result in a Vector :help => "first argument, two " * "entries at once", :required => true), "arg2", Dict( :nargs => '*', # eats up as many arguments as possible before an option :default => Any["no_arg_given"], # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) :help => "second argument, eats up " * "as many items as possible " * "before an option") ) return s end function ap_settings2c() s = ArgParseSettings(description = "Test 2 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) @add_arg_table!(s , "--opt1" , nargs = '?' # '?' means optional argument , arg_type = Int # only Int arguments allowed , default = 0 # this is used when the option is not passed , constant = 1 # this is used if --opt1 is paseed with no argument , help = "an option" , "-O" , arg_type = Symbol , default = :xyz , help = "another option" , ["--flag", "-f"] , action = :store_true # this makes it a flag , help = "a flag" , ["--karma", "-k"] , action = :count_invocations # increase a counter each time the option is given , help = "increase karma" , "arg1" , nargs = 2 # eats up two arguments; puts the result in a Vector , help = "first argument, two " * "entries at once" , required = true , "arg2" , nargs = '*' # eats up as many arguments as possible before an option , default = Any["no_arg_given"] # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) , help = "second argument, eats up " * "as many items as possible " * "before an option" ) return s end function ap_settings2d() s = ArgParseSettings(description = "Test 2 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) @add_arg_table! s begin ("--opt1"; nargs = '?'; # '?' means optional argument arg_type = Int; # only Int arguments allowed default = 0; # this is used when the option is not passed constant = 1; # this is used if --opt1 is paseed with no argument help = "an option"), ("-O"; arg_type = Symbol; default = :xyz; help = "another option"), (["--flag", "-f"]; action = :store_true; # this makes it a flag help = "a flag") (["--karma", "-k"]; action = :count_invocations; # increase a counter each time the option is given help = "increase karma") ("arg1"; nargs = 2; # eats up two arguments; puts the result in a Vector help = "first argument, two " * "entries at once"; required = true) ("arg2"; nargs = '*'; # eats up as many arguments as possible before an option default = Any["no_arg_given"]; # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) help = "second argument, eats up " * "as many items as possible " * "before an option") end return s end function ap_settings2e() s = ArgParseSettings(description = "Test 2 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) @add_arg_table!(s, "--opt1", begin nargs = '?' # '?' means optional argument arg_type = Int # only Int arguments allowed default = 0 # this is used when the option is not passed constant = 1 # this is used if --opt1 is paseed with no argument help = "an option" end, "-O", begin arg_type = Symbol default = :xyz help = "another option" end, ["--flag", "-f"], begin action = :store_true # this makes it a flag help = "a flag" end, ["--karma", "-k"], begin action = :count_invocations # increase a counter each time the option is given help = "increase karma" end, "arg1", begin nargs = 2 # eats up two arguments; puts the result in a Vector help = "first argument, two " * "entries at once" required = true end, "arg2", begin nargs = '*' # eats up as many arguments as possible before an option default = Any["no_arg_given"] # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) help = "second argument, eats up " * "as many items as possible " * "before an option" end) return s end for s = [ap_settings2(), ap_settings2b(), ap_settings2c(), ap_settings2d(), ap_settings2e()] ap_test2(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--opt1 [OPT1]] [-O O] [-f] [-k] arg1 arg1 [arg2...] Test 2 for ArgParse.jl positional arguments: arg1 first argument, two entries at once arg2 second argument, eats up as many items as possible before an option (default: Any["no_arg_given"]) optional arguments: --opt1 [OPT1] an option (type: $Int, default: 0, without arg: 1) -O O another option (type: $Symbol, default: :xyz) -f, --flag a flag -k, --karma increase karma Have fun! """ @test stringversion(s) == "Version 1.0\n" @ap_test_throws ap_test2([]) @test ap_test2(["X", "Y"]) == Dict{String,Any}("opt1"=>0, "O"=>:xyz, "flag"=>false, "karma"=>0, "arg1"=>Any["X", "Y"], "arg2"=>Any["no_arg_given"]) @test ap_test2(["X", "Y", "-k", "-f", "Z", "--karma", "--opt"]) == Dict{String,Any}("opt1"=>1, "O"=>:xyz, "flag"=>true, "karma"=>2, "arg1"=>Any["X", "Y"], "arg2"=>Any["Z"]) @test ap_test2(["X", "Y", "--opt", "-k", "-f", "Z", "--karma"]) == Dict{String,Any}("opt1"=>1, "O"=>:xyz, "flag"=>true, "karma"=>2, "arg1"=>Any["X", "Y"], "arg2"=>Any["Z"]) @test ap_test2(["X", "Y", "--opt", "--karma", "-O", "XYZ", "-f", "Z", "-k"]) == Dict{String,Any}("opt1"=>1, "O"=>:XYZ, "flag"=>true, "karma"=>2, "arg1"=>Any["X", "Y"], "arg2"=>Any["Z"]) @test ap_test2(["--opt", "-3", "X", "Y", "-k", "-f", "Z", "-O", "a b c", "--karma"]) == Dict{String,Any}("opt1"=>-3, "O"=>Symbol("a b c"), "flag"=>true, "karma"=>2, "arg1"=>Any["X", "Y"], "arg2"=>Any["Z"]) @ap_test_throws ap_test2(["--opt"]) @ap_test_throws ap_test2(["--opt="]) @ap_test_throws ap_test2(["--opt", "", "X", "Y"]) @ap_test_throws ap_test2(["--opt", "1e-2", "X", "Y"]) @aps_test_throws @add_arg_table!(s, "required_arg_after_optional_args", required = true) # wrong default @aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, default = 1.5) @aps_test_throws @add_arg_table!(s, "--opt3", arg_type = Function, default = "string") # wrong range tester @aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, range_tester = x->string(x), default = 1) @aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, range_tester = x->sqrt(x)<1, default = -1) end end ================================================ FILE: test/argparse_test03.jl ================================================ # test 03: dest_name, metavar, range_tester, alternative # actions, custom parser struct CustomType end @testset "test 03" begin function ArgParse.parse_item(::Type{CustomType}, x::AbstractString) @assert x == "custom" return CustomType() end Base.show(io::IO, ::Type{CustomType}) = print(io, "CustomType") Base.show(io::IO, c::CustomType) = print(io, "CustomType()") function ap_settings3() s = ArgParseSettings("Test 3 for ArgParse.jl", exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "--opt1" action = :append_const # appends 'constant' to 'dest_name' arg_type = String constant = "O1" dest_name = "O_stack" # this changes the destination help = "append O1" "--opt2" action = :append_const arg_type = String constant = "O2" dest_name = "O_stack" # same dest_name as opt1, different constant help = "append O2" "-k" action = :store_const # stores constant if given, default otherwise default = 0 constant = 42 help = "provide the answer" "-u" action = :store_const # stores constant if given, default otherwise default = 0 constant = 42.0 help = "provide the answer as floating point" "--array" default = [7, 3, 2] arg_type = Vector{Int} eval_arg = true # enables evaluation of the argument. NOTE: security risk! help = "create an array" "--custom" default = CustomType() arg_type = CustomType # uses the user-defined version of ArgParse.parse_item help = "the only accepted argument is \"custom\"" "--oddint" default = 1 arg_type = Int range_tester = x->(isodd(x) || error("not odd")) # range error ≡ false help = "an odd integer" "--collect" action = :append_arg arg_type = Int metavar = "C" help = "collect things" "--awkward-option" nargs = '+' # eats up as many argument as found (at least 1) action = :append_arg # argument chunks are appended when the option is # called repeatedly dest_name = "awk" range_tester = (x->x=="X"||x=="Y") # each argument must be either "X" or "Y" default = [["X"]] metavar = "XY" help = "either X or Y; all XY's are " * "stored in chunks" end return s end let s = ap_settings3() ap_test3(args) = parse_args(args, s) ## ugly workaround for the change of printing Vectors in julia 1.6, ## from Array{Int,1} to Vector{Int} array_help_lines = if string(Vector{Any}) == "Vector{Any}" """ --array ARRAY create an array (type: Vector{$Int}, default: $([7, 3, 2])) """ else """ --array ARRAY create an array (type: Array{$Int,1}, default: $([7, 3, 2])) """ end array_help_lines = array_help_lines[1:end-1] # remove an extra newline @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--opt1] [--opt2] [-k] [-u] [--array ARRAY] [--custom CUSTOM] [--oddint ODDINT] [--collect C] [--awkward-option XY [XY...]] Test 3 for ArgParse.jl optional arguments: --opt1 append O1 --opt2 append O2 -k provide the answer -u provide the answer as floating point $array_help_lines --custom CUSTOM the only accepted argument is "custom" (type: CustomType, default: CustomType()) --oddint ODDINT an odd integer (type: $Int, default: 1) --collect C collect things (type: $Int) --awkward-option XY [XY...] either X or Y; all XY's are stored in chunks (default: $(Vector{Any})[["X"]]) """ @test ap_test3([]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Any[Any["X"]]) @test ap_test3(["--opt1", "--awk", "X", "X", "--opt2", "--opt2", "-k", "--coll", "5", "-u", "--array=[4]", "--custom", "custom", "--collect", "3", "--awkward-option=Y", "X", "--opt1", "--oddint", "-1"]) == Dict{String,Any}("O_stack"=>String["O1", "O2", "O2", "O1"], "k"=>42, "u"=>42.0, "array"=>[4], "custom"=>CustomType(), "oddint"=>-1, "collect"=>[5, 3], "awk"=>Any[Any["X"], Any["X", "X"], Any["Y", "X"]]) @ap_test_throws ap_test3(["X"]) @ap_test_throws ap_test3(["--awk", "Z"]) @ap_test_throws ap_test3(["--awk", "-2"]) @ap_test_throws ap_test3(["--array", "7"]) @ap_test_throws ap_test3(["--custom", "default"]) @ap_test_throws ap_test3(["--oddint", "0"]) @ap_test_throws ap_test3(["--collect", "0.5"]) # invalid option name @aps_test_throws @add_arg_table!(s, "-2", action = :store_true) # wrong constants @aps_test_throws @add_arg_table!(s, "--opt", action = :store_const, arg_type = Int, default = 1, constant = 1.5) @aps_test_throws @add_arg_table!(s, "--opt", action = :append_const, arg_type = Int, constant = 1.5) # wrong defaults @aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, arg_type = Int, default = String["hello"]) @aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, nargs = '+', arg_type = Int, default = Vector{Float64}[[1.5]]) @aps_test_throws @add_arg_table!(s, "--opt", action = :store_arg, nargs = '+', arg_type = Int, default = [1.5]) @aps_test_throws @add_arg_table!(s, "--opt", action = :store_arg, nargs = '+', arg_type = Int, default = 1) @aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, arg_type = Int, range_tester=x->x<=1, default = Int[0, 1, 2]) @aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, nargs = '+', arg_type = Int, range_tester=x->x<=1, default = Vector{Int}[[1,1],[0,2]]) @aps_test_throws @add_arg_table!(s, "--opt", action = :store_arg, nargs = '+', range_tester = x->x<=1, default = [1.5]) # no constants @aps_test_throws @add_arg_table!(s, "--opt", action = :store_const, arg_type = Int, default = 1) @aps_test_throws @add_arg_table!(s, "--opt", action = :append_const, arg_type = Int) # incompatible action @aps_test_throws @add_arg_table!(s, "--opt3", action = :store_const, arg_type = String, constant = "O3", dest_name = "O_stack", help = "append O3") # wrong range tester @aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, arg_type = Int, range_tester=x->string(x), default = Int[0, 1, 2]) @aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, nargs = '+', arg_type = Int, range_tester=x->string(x), default = Vector{Int}[[1,1],[0,2]]) @aps_test_throws @add_arg_table!(s, "--opt", action = :store_arg, nargs = '+', range_tester = x->string(x), default = [1.5]) @aps_test_throws @add_arg_table!(s, "--opt", action = :store_arg, nargs = '+', range_tester = x->sqrt(x)<2, default = [-1.5]) @aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, nargs = '+', arg_type = Int, range_tester=x->sqrt(x)<2, default = Vector{Int}[[1,1],[0,-2]]) # allow ambiguous options s.allow_ambiguous_opts = true @add_arg_table!(s, "-2", action = :store_true) @test ap_test3([]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Vector{Any}[["X"]], "2"=>false) @test ap_test3(["-2"]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Vector{Any}[["X"]], "2"=>true) @test ap_test3(["--awk", "X", "-2"]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Vector{Any}[["X"], ["X"]], "2"=>true) @ap_test_throws ap_test3(["--awk", "X", "-3"]) end end ================================================ FILE: test/argparse_test04.jl ================================================ # test 04: manual help/version, import another parser @testset "test 04" begin function ap_settings4() s0 = ArgParseSettings() # a "parent" structure e.g. one with some useful set of rules # which we want to extend # So we just add a simple table @add_arg_table! s0 begin "--parent-flag", "-o" action = "store_true" help = "parent flag" "--flag" action = "store_true" help = "another parent flag" "parent-argument" help = "parent argument" "will-be-overridden" metavar = "ARG" end s = ArgParseSettings("Test 4 for ArgParse.jl", add_help = false, # disable auto-add of --help option version = "Version 1.0", # we set the version info, but --version won't be added error_on_conflict = false, # do not error-out when trying to override an option exc_handler = ArgParse.debug_handler) import_settings!(s, s0) # now s has all of s0 arguments (except help/version) @add_arg_table! s begin "-o" # this will partially override s0's --parent-flag action = :store_true help = "child flag" "--flag" # this will fully override s0's --flag action = :store_true help = "another child flag" "-?", "--HELP", "--¡ḧëļṕ" # (almost) all characters allowed action = :show_help # will invoke the help generator help = "this will help you" "-v", "--VERSION" action = :show_version # will show version information help = "show version information " * "and exit" "child-argument" # same metavar as "will-be-overridden" metavar = "ARG" help = "child argument" end return s end function ap_settings4b() # same as ap_settings4(), but imports all settings s0 = ArgParseSettings(add_help = false, error_on_conflict = false, exc_handler = ArgParse.debug_handler) # So we just add a simple table @add_arg_table! s0 begin "--parent-flag", "-o" action = "store_true" help = "parent flag" "--flag" action = "store_true" help = "another parent flag" "parent-argument" help = "parent argument" "will-be-overridden" metavar = "ARG" end s = ArgParseSettings("Test 4 for ArgParse.jl", version = "Version 1.0") import_settings!(s, s0, args_only=false) @add_arg_table! s begin "-o" action = :store_true help = "child flag" "--flag" action = :store_true help = "another child flag" "-?", "--HELP", "--¡ḧëļṕ" action = :show_help help = "this will help you" "-v", "--VERSION" action = :show_version help = "show version information " * "and exit" "child-argument" metavar = "ARG" help = "child argument" end return s end for s = [ap_settings4(), ap_settings4b()] ap_test4(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--parent-flag] [-o] [--flag] [-?] [-v] [parent-argument] [ARG] Test 4 for ArgParse.jl positional arguments: parent-argument parent argument ARG child argument optional arguments: --parent-flag parent flag -o child flag --flag another child flag -?, --HELP, --¡ḧëļṕ this will help you -v, --VERSION show version information and exit """ @test stringversion(s) == "Version 1.0\n" @test ap_test4([]) == Dict{String,Any}("parent-flag"=>false, "o"=>false, "flag"=>false, "parent-argument"=>nothing, "child-argument"=>nothing) @test ap_test4(["-o", "X"]) == Dict{String,Any}("parent-flag"=>false, "o"=>true, "flag"=>false, "parent-argument"=>"X", "child-argument"=>nothing) @ap_test_throws ap_test4(["-h"]) # same metavar as another argument s.error_on_conflict = true @aps_test_throws @add_arg_table!(s, "other-arg", metavar="parent-argument") end function ap_settings4_base() s = ArgParseSettings("Test 4 for ArgParse.jl", add_help = false, version = "Version 1.0", exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "-o" action = :store_true help = "child flag" "--flag" action = :store_true help = "another child flag" end return s end function ap_settings4_conflict1() s0 = ArgParseSettings() @add_arg_table! s0 begin "--parent-flag", "-o" action = "store_true" help = "parent flag" end return s0 end function ap_settings4_conflict2() s0 = ArgParseSettings() @add_arg_table! s0 begin "--flag" action = "store_true" help = "another parent flag" end return s0 end let s = ap_settings4_base() for s0 = [ap_settings4_conflict1(), ap_settings4_conflict2()] @aps_test_throws import_settings!(s, s0) end end end ================================================ FILE: test/argparse_test05.jl ================================================ # test 05: commands & subtables @testset "test 05" begin function ap_settings5() s = ArgParseSettings("Test 5 for ArgParse.jl", exc_handler = ArgParse.debug_handler, exit_after_help = false) @add_arg_table! s begin "run" action = :command help = "start running mode" "jump", "ju", "J" action = :command help = "start jumping mode" end @add_arg_table! s["run"] begin "--speed" arg_type = Float64 default = 10.0 help = "running speed, in Å/month" end s["jump"].description = "Jump mode" s["jump"].commands_are_required = false s["jump"].autofix_names = true @add_arg_table! s["jump"] begin "--higher" action = :store_true help = "enhance jumping" "--somersault", "-s" action = :command dest_name = "som" help = "somersault jumping mode" "--clap-feet", "-c" action = :command help = "clap feet jumping mode" end s["jump"]["som"].description = "Somersault jump mode" @add_arg_table! s["jump"]["som"] begin "-t" nargs = '?' arg_type = Int default = 1 constant = 1 help = "twist a number of times" "-b" action = :store_true help = "blink" end return s end let s = ap_settings5() ap_test5(args; kw...) = parse_args(args, s; kw...) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) {run|jump} Test 5 for ArgParse.jl commands: run start running mode jump start jumping mode (aliases: ju, J) """ @test stringhelp(s["run"]) == """ usage: $(basename(Base.source_path())) run [--speed SPEED] optional arguments: --speed SPEED running speed, in Å/month (type: Float64, default: 10.0) """ @test stringhelp(s["jump"]) == """ usage: $(basename(Base.source_path())) jump [--higher] [-s|-c] Jump mode commands: -s, --somersault somersault jumping mode -c, --clap-feet clap feet jumping mode optional arguments: --higher enhance jumping """ @test stringhelp(s["jump"]["som"]) == """ usage: $(basename(Base.source_path())) jump -s [-t [T]] [-b] Somersault jump mode optional arguments: -t [T] twist a number of times (type: $Int, default: 1, without arg: 1) -b blink """ @ap_test_throws ap_test5([]) @noout_test ap_test5(["--help"]) ≡ nothing @test ap_test5(["run", "--speed", "3"]) == Dict{String,Any}("%COMMAND%"=>"run", "run"=>Dict{String,Any}("speed"=>3.0)) @noout_test ap_test5(["jump", "--help"]) ≡ nothing @test ap_test5(["jump"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>nothing)) @test ap_test5(["jump", "--higher", "--clap"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>true, "%COMMAND%"=>"clap_feet", "clap_feet"=>Dict{String,Any}())) @test ap_test5(["ju", "--higher", "--clap"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>true, "%COMMAND%"=>"clap_feet", "clap_feet"=>Dict{String,Any}())) @test ap_test5(["J", "--higher", "--clap"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>true, "%COMMAND%"=>"clap_feet", "clap_feet"=>Dict{String,Any}())) @noout_test ap_test5(["jump", "--higher", "--clap", "--help"]) ≡ nothing @noout_test ap_test5(["jump", "--higher", "--clap", "--help"], as_symbols = true) ≡ nothing @ap_test_throws ap_test5(["jump", "--clap", "--higher"]) @test ap_test5(["jump", "--somersault"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>1, "b"=>false))) @test ap_test5(["jump", "-s", "-t"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>1, "b"=>false))) @test ap_test5(["jump", "-st"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>1, "b"=>false))) @test ap_test5(["jump", "-sbt"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>1, "b"=>true))) @test ap_test5(["jump", "-s", "-t2"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>2, "b"=>false))) @test ap_test5(["jump", "-sbt2"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>2, "b"=>true))) @test ap_test5(["ju", "-sbt2"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>2, "b"=>true))) @test ap_test5(["J", "-sbt2"]) == Dict{String,Any}("%COMMAND%"=>"jump", "jump"=>Dict{String,Any}("higher"=>false, "%COMMAND%"=>"som", "som"=>Dict{String,Any}("t"=>2, "b"=>true))) @noout_test ap_test5(["jump", "-sbht2"]) ≡ nothing @ap_test_throws ap_test5(["jump", "-st2b"]) @ap_test_throws ap_test5(["jump", "-stb"]) @ap_test_throws ap_test5(["jump", "-sb-"]) @ap_test_throws ap_test5(["jump", "-s-b"]) @ap_test_throws ap_test5(["ju", "-s-b"]) @test ap_test5(["jump", "--higher"], as_symbols = true) == Dict{Symbol,Any}(:_COMMAND_=>:jump, :jump=>Dict{Symbol,Any}(:higher=>true, :_COMMAND_=>nothing)) @test ap_test5(["run", "--speed", "3"], as_symbols = true) == Dict{Symbol,Any}(:_COMMAND_=>:run, :run=>Dict{Symbol,Any}(:speed=>3.0)) # argument after command @aps_test_throws @add_arg_table!(s, "arg_after_command") # same name as command @aps_test_throws @add_arg_table!(s, "run") @aps_test_throws @add_arg_table!(s["jump"], "-c") @aps_test_throws @add_arg_table!(s["jump"], "--somersault") # same dest_name as command @aps_test_throws @add_arg_table!(s["jump"], "--som") @aps_test_throws @add_arg_table!(s["jump"], "-s", dest_name = "som") # same name as command alias @aps_test_throws @add_arg_table!(s, "ju") @aps_test_throws @add_arg_table!(s, "J") # new command with the same name as another one @aps_test_throws @add_arg_table!(s, ["run", "R"], action = :command) @aps_test_throws @add_arg_table!(s, "jump", action = :command) # new command with the same name as another one's alias @aps_test_throws @add_arg_table!(s, "ju", action = :command) @aps_test_throws @add_arg_table!(s, "J", action = :command) # new command with an alias which is the same as another command @aps_test_throws @add_arg_table!(s, ["fast", "run"], action = :command) @aps_test_throws @add_arg_table!(s, ["R", "jump"], action = :command) # new command with an alias which is already in use @aps_test_throws @add_arg_table!(s, ["R", "ju"], action = :command) @aps_test_throws @add_arg_table!(s, ["R", "S", "J"], action = :command) # alias overriding by a command name @add_arg_table!(s, "J", action = :command, force_override = true, help = "the J command") @test stringhelp(s) == """ usage: $(basename(Base.source_path())) {run|jump|J} Test 5 for ArgParse.jl commands: run start running mode jump start jumping mode (aliases: ju) J the J command """ # alias overriding by a command alias @add_arg_table!(s, ["S", "ju"], action = :command, force_override = true, help = "the S command") @test stringhelp(s) == """ usage: $(basename(Base.source_path())) {run|jump|J|S} Test 5 for ArgParse.jl commands: run start running mode jump start jumping mode J the J command S the S command (aliases: ju) """ # cannot override a command name @aps_test_throws @add_arg_table!(s, ["J", "R"], action = :command, force_override = true) @aps_test_throws @add_arg_table!(s, ["R", "J"], action = :command, force_override = true) # conflict between dest_name and a reserved Symbol @add_arg_table!(s, "--COMMAND", dest_name="_COMMAND_") @aps_test_throws ap_test5(["run", "--speed", "3"], as_symbols = true) end function ap_settings5b() s0 = ArgParseSettings() s = ArgParseSettings(error_on_conflict = false, exc_handler = ArgParse.debug_handler, exit_after_help = false) @add_arg_table! s0 begin "run", "R" action = :command help = "start running mode" "jump", "ju" action = :command help = "start jumping mode" "--time" arg_type = String default = "now" metavar = "T" help = "time at which to " * "perform the command" end @add_arg_table! s0["run"] begin "--speed" arg_type = Float64 default = 10. help = "running speed, in Å/month" end s0["jump"].description = "Jump mode" s0["jump"].commands_are_required = false s0["jump"].autofix_names = true s0["jump"].add_help = false add_arg_group!(s0["jump"], "modifiers", "modifiers") set_default_arg_group!(s0["jump"]) @add_arg_table! s0["jump"] begin "--higher" action = :store_true help = "enhance jumping" group = "modifiers" "--somersault" action = :command dest_name = "som" help = "somersault jumping mode" "--clap-feet", "-c" action = :command help = "clap feet jumping mode" end add_arg_group!(s0["jump"], "other") @add_arg_table! s0["jump"] begin "--help" action = :show_help help = "show this help message " * "and exit" end s0["jump"]["som"].description = "Somersault jump mode" @add_arg_table! s begin "jump", "run", "J" # The "run" alias will be overridden action = :command help = "start jumping mode" "fly", "R" # The "R" alias will be overridden action = :command help = "start flying mode" # next opt will be overridden (same dest_name as s0's --time, # incompatible arg_type) "-T" dest_name = "time" arg_type = Int end s["jump"].autofix_names = true s["jump"].add_help = false add_arg_group!(s["jump"], "modifiers", "modifiers") @add_arg_table! s["jump"] begin "--lower" action = :store_false dest_name = "higher" help = "reduce jumping" end set_default_arg_group!(s["jump"]) @add_arg_table! s["jump"] begin "--clap-feet" action = :command help = "clap feet jumping mode" "--som", "-s" # will be overridden (same dest_name as s0 command) action = :store_true help = "overridden" "--somersault" # will be overridden (same option as s0 command) action = :store_true help = "overridden" end @add_arg_table! s["fly"] begin "--glade" action = :store_true help = "glade mode" end s["jump"]["clap_feet"].add_version = true @add_arg_table! s["jump"]["clap_feet"] begin "--whistle" action = :store_true end import_settings!(s, s0) return s end let s = ap_settings5b() ap_test5b(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--time T] {jump|fly|run} commands: jump start jumping mode (aliases: J, ju) fly start flying mode run start running mode (aliases: R) optional arguments: --time T time at which to perform the command (default: "now") """ @test stringhelp(s["jump"]) == """ usage: $(basename(Base.source_path())) jump [--lower] [--higher] [--help] {-c|--somersault} commands: -c, --clap-feet clap feet jumping mode --somersault somersault jumping mode modifiers: --lower reduce jumping --higher enhance jumping other: --help show this help message and exit """ @ap_test_throws ap_test5b([]) @test ap_test5b(["fly"]) == Dict{String,Any}("%COMMAND%"=>"fly", "time"=>"now", "fly"=>Dict{String,Any}("glade"=>false)) @test ap_test5b(["jump", "--lower", "--clap"]) == Dict{String,Any}("%COMMAND%"=>"jump", "time"=>"now", "jump"=>Dict{String,Any}("%COMMAND%"=>"clap_feet", "higher"=>false, "clap_feet"=>Dict{String,Any}("whistle"=>false))) @test ap_test5b(["ju", "--lower", "--clap"]) == Dict{String,Any}("%COMMAND%"=>"jump", "time"=>"now", "jump"=>Dict{String,Any}("%COMMAND%"=>"clap_feet", "higher"=>false, "clap_feet"=>Dict{String,Any}("whistle"=>false))) @test ap_test5b(["J", "--lower", "--clap"]) == Dict{String,Any}("%COMMAND%"=>"jump", "time"=>"now", "jump"=>Dict{String,Any}("%COMMAND%"=>"clap_feet", "higher"=>false, "clap_feet"=>Dict{String,Any}("whistle"=>false))) @noout_test ap_test5b(["jump", "--lower", "--help"]) ≡ nothing @noout_test ap_test5b(["jump", "--lower", "--clap", "--version"]) ≡ nothing @ap_test_throws ap_test5b(["jump"]) @test ap_test5b(["run", "--speed=3"]) == Dict{String,Any}("%COMMAND%"=>"run", "time"=>"now", "run"=>Dict{String,Any}("speed"=>3.0)) @test ap_test5b(["R", "--speed=3"]) == Dict{String,Any}("%COMMAND%"=>"run", "time"=>"now", "run"=>Dict{String,Any}("speed"=>3.0)) end let s1 = @add_arg_table!(ArgParseSettings(), "run", action = :command) s2 = @add_arg_table!(ArgParseSettings(), "--run", action = :store_true) @aps_test_throws import_settings!(s1, s2) @aps_test_throws import_settings!(s2, s1) # this fails since error_on_conflict=true s2 = @add_arg_table!(ArgParseSettings(), ["R", "run"], action = :command) @aps_test_throws import_settings!(s1, s2) @aps_test_throws import_settings!(s2, s1) # this fails since error_on_conflict=true end end ================================================ FILE: test/argparse_test06.jl ================================================ # test 06: argument groups @testset "test 06" begin function ap_settings6() s = ArgParseSettings("Test 6 for ArgParse.jl", exc_handler = ArgParse.debug_handler) add_arg_group!(s, "stack options") @add_arg_table! s begin "--opt1" action = :append_const arg_type = String constant = "O1" dest_name = "O_stack" help = "append O1 to the stack" "--opt2" action = :append_const arg_type = String constant = "O2" dest_name = "O_stack" help = "append O2 to the stack" end add_arg_group!(s, "weird options", "weird") set_default_arg_group!(s, "weird") @add_arg_table! s begin "--awkward-option" nargs = '+' action = :append_arg dest_name = "awk" arg_type = String range_tester = (x->x=="X"||x=="Y") metavar = "XY" help = "either X or Y; all XY's are " * "stored in chunks" end set_default_arg_group!(s) @add_arg_table! s begin "-k" action = :store_const default = 0 constant = 42 help = "provide the answer" "--şİłłÿ" nargs = 3 help = "an option with a silly name, " * "which expects 3 arguments" metavar = "☺" group = "weird" end set_default_arg_group!(s, "weird") @add_arg_table! s begin "--rest" nargs = 'R' help = "an option which will consume " * "all following arguments" end return s end let s = ap_settings6() ap_test6(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--opt1] [--opt2] [--awkward-option XY [XY...]] [-k] [--şİłłÿ ☺ ☺ ☺] [--rest [REST...]] Test 6 for ArgParse.jl optional arguments: -k provide the answer stack options: --opt1 append O1 to the stack --opt2 append O2 to the stack weird options: --awkward-option XY [XY...] either X or Y; all XY's are stored in chunks --şİłłÿ ☺ ☺ ☺ an option with a silly name, which expects 3 arguments --rest [REST...] an option which will consume all following arguments """ @test ap_test6([]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "awk"=>Vector{Any}[], "şİłłÿ"=>Any[], "rest"=>[]) @test ap_test6(["--opt1", "--awk", "X", "X", "--opt2", "--opt2", "-k", "--awkward-option=Y", "X", "--opt1", "--şİł=-1", "-2", "-3"]) == Dict{String,Any}("O_stack"=>String["O1", "O2", "O2", "O1"], "k"=>42, "awk"=>Any[Any["X", "X"], Any["Y", "X"]], "şİłłÿ"=>["-1", "-2", "-3"], "rest"=>[]) @test ap_test6(["--opt1", "--awk", "X", "X", "--opt2", "--opt2", "--r", "-k", "--awkward-option=Y", "X", "--opt1", "--şİł", "-1", "-2", "-3"]) == Dict{String,Any}("O_stack"=>String["O1", "O2", "O2"], "k"=>0, "awk"=>Any[Any["X", "X"]], "şİłłÿ"=>[], "rest"=>Any["-k", "--awkward-option=Y", "X", "--opt1", "--şİł", "-1", "-2", "-3"]) @test ap_test6(["--opt1", "--awk", "X", "X", "--opt2", "--opt2", "--r=-k", "--awkward-option=Y", "X", "--opt1", "--şİł", "-1", "-2", "-3"]) == Dict{String,Any}("O_stack"=>String["O1", "O2", "O2"], "k"=>0, "awk"=>Any[Any["X", "X"]], "şİłłÿ"=>[], "rest"=>Any["-k", "--awkward-option=Y", "X", "--opt1", "--şİł", "-1", "-2", "-3"]) @ap_test_throws ap_test6(["X"]) @ap_test_throws ap_test6(["--awk"]) @ap_test_throws ap_test6(["--awk", "Z"]) @ap_test_throws ap_test6(["--şİł", "-1", "-2"]) @ap_test_throws ap_test6(["--şİł", "-1", "-2", "-3", "-4"]) # invalid groups @aps_test_throws add_arg_group!(s, "invalid commands", "") @aps_test_throws add_arg_group!(s, "invalid commands", "#invalid") @aps_test_throws @add_arg_table!(s, "--opt", action = :store_true, group = "none") end end ================================================ FILE: test/argparse_test07.jl ================================================ # test 07: required options, line breaks in desc/epilog @testset "test 07" begin function ap_settings7() s = ArgParseSettings(description = "Test 7 for ArgParse.jl\n\nTesting oxymoronic options", exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "--oxymoronic", "-x" required = true help = "a required option" "--opt" required = false help = "a true option" "--flag", "-f" action = :store_true help = "a flag" "-o" required = true help = "yet another oxymoronic option" end s.epilog = """ Example:\n \n \ua0\ua0 --oxymoronic X -o 1\n \n Not a particularly enlightening example, but on the other hand this program does not really do anything useful. """ return s end let s = ap_settings7() ap_test7(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) -x OXYMORONIC [--opt OPT] [-f] -o O Test 7 for ArgParse.jl Testing oxymoronic options optional arguments: -x, --oxymoronic OXYMORONIC a required option --opt OPT a true option -f, --flag a flag -o O yet another oxymoronic option Example: --oxymoronic X -o 1 Not a particularly enlightening example, but on the other hand this program does not really do anything useful. """ @ap_test_throws ap_test7([]) @test ap_test7(["--oxymoronic=A", "-o=B"]) == Dict{String,Any}("oxymoronic"=>"A", "opt"=>nothing, "o"=>"B", "flag"=>false) @ap_test_throws ap_test7(["--oxymoronic=A", "--opt=B"]) @ap_test_throws ap_test7(["--opt=A, -o=B"]) s.suppress_warnings = true add_arg_table!(s, "-g", Dict(:action=>:store_true, :required=>true)) @test ap_test7(["--oxymoronic=A", "-o=B"]) == Dict{String,Any}("oxymoronic"=>"A", "opt"=>nothing, "o"=>"B", "flag"=>false, "g"=>false) end end ================================================ FILE: test/argparse_test08.jl ================================================ # test 08: read args from file, read version from project @testset "test 08" begin function ap_settings8a() s = ArgParseSettings(fromfile_prefix_chars=['@']) @add_arg_table! s begin "--opt1" # an option (will take an argument) "--opt2", "-o" # another option, with short form "arg1" # a positional argument end s.exc_handler = ArgParse.debug_handler return s end function ap_settings8b() # unicode s = ArgParseSettings(fromfile_prefix_chars="@∘") @add_arg_table! s begin "--opt1" # an option (will take an argument) "--opt2", "-o" # another option, with short form "arg1" # a positional argument end s.exc_handler = ArgParse.debug_handler return s end let s = ap_settings8a() ap_test8(args) = parse_args(args, s) @test ap_test8(["@args-file1"]) == Dict( "opt1"=>nothing, "opt2"=>"y", "arg1"=>nothing) @test ap_test8(["@args-file1", "arg"]) == Dict( "opt1"=>nothing, "opt2"=>"y", "arg1"=>"arg") @test ap_test8(["@args-file2"]) == Dict( "opt1"=>"x", "opt2"=>"y", "arg1"=>nothing) @test ap_test8(["@args-file2", "arg"]) == Dict( "opt1"=>"x", "opt2"=>"y", "arg1"=>"arg") end let s = ap_settings8b() ap_test8(args) = parse_args(args, s) @test ap_test8(["∘args-file1"]) == Dict( "opt1"=>nothing, "opt2"=>"y", "arg1"=>nothing) @test ap_test8(["@args-file1", "arg"]) == Dict( "opt1"=>nothing, "opt2"=>"y", "arg1"=>"arg") @test ap_test8(["∘args-file2"]) == Dict( "opt1"=>"x", "opt2"=>"y", "arg1"=>nothing) @test ap_test8(["@args-file2", "arg"]) == Dict( "opt1"=>"x", "opt2"=>"y", "arg1"=>"arg") end # not allowed @ap_test_throws ArgParseSettings(fromfile_prefix_chars=['-']) @ap_test_throws ArgParseSettings(fromfile_prefix_chars=['Å']) @ap_test_throws ArgParseSettings(fromfile_prefix_chars=['8']) # default project version @test stringversion(ArgParseSettings( add_version = true, version = @project_version )) == "1.0.0\n" # project version from filepath @test stringversion(ArgParseSettings( add_version = true, version = @project_version "Project.toml" )) == "1.0.0\n" # project version from expression that returns a filepath @test stringversion(ArgParseSettings( add_version = true, version = @project_version(".", "Project.toml") )) == "1.0.0\n" # throws an error if the file doesn't contain a version @test_throws ArgumentError ArgParse.project_version("args-file1") end ================================================ FILE: test/argparse_test09.jl ================================================ # test 09: preformatted desc/epilog @testset "test 09" begin function ap_settings9() s = ArgParseSettings(description = """ Test 9 for ArgParse.jl Testing preformatted description/epilog 1 1 1 1 2 1 1 3 3 1 """, preformatted_description=true, exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "--opt" required = true help = "a required option" end s.epilog = """ Example: --opt X - one - two - three """ s.preformatted_epilog = true return s end let s = ap_settings9() ap_test7(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) --opt OPT Test 9 for ArgParse.jl Testing preformatted description/epilog 1 1 1 1 2 1 1 3 3 1 optional arguments: --opt OPT a required option Example: --opt X - one - two - three """ @ap_test_throws ap_test7([]) @test ap_test7(["--opt=A"]) == Dict{String,Any}("opt"=>"A") end end ================================================ FILE: test/argparse_test10.jl ================================================ # test 10: multiple metavars # function version of add_arg_table! @testset "test 10" begin function ap_settings10() s = ArgParseSettings(description = "Test 10 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "--opt1" nargs => 2 # exactly 2 arguments must be specified arg_type => Int # only Int arguments allowed default => [0, 1] # this is used when the option is not passed metavar => ["A", "B"] # two metavars for two arguments help => "an option" "--flag", "-f" action := :store_true # this makes it a flag help := "a flag" "--karma", "-k" action => :count_invocations # increase a counter each time the option is given help => "increase karma" "arg1" nargs => 2 # eats up two arguments; puts the result in a Vector help => "first argument, two " * "entries at once" required => true "arg2" nargs => '*' # eats up as many arguments as possible before an option default => Any["no_arg_given"] # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) help => "second argument, eats up " * "as many items as possible " * "before an option" end return s end function ap_settings10b() s = ArgParseSettings(description = "Test 10 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) add_arg_table!(s, "--opt1", Dict( :nargs => 2, # exactly 2 arguments :arg_type => Int, # only Int arguments allowed :default => [0, 1], # this is used when the option is not passed :metavar => ["A", "B"], # two metavars for two arguments :help => "an option"), ["--flag", "-f"], Dict( :action => :store_true, # this makes it a flag :help => "a flag"), ["--karma", "-k"], Dict( :action => :count_invocations, # increase a counter each time the option is given :help => "increase karma"), "arg1", Dict( :nargs => 2, # eats up two arguments; puts the result in a Vector :help => "first argument, two " * "entries at once", :required => true), "arg2", Dict( :nargs => '*', # eats up as many arguments as possible before an option :default => Any["no_arg_given"], # since the result will be a Vector{Any}, the default must # also be (or it can be [] or nothing) :help => "second argument, eats up " * "as many items as possible " * "before an option") ) return s end # test to ensure length of vector is the same as nargs function ap_settings10c(in_nargs) s = ArgParseSettings(description = "Test 10 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) add_arg_table!(s, "--opt1", Dict( :nargs => in_nargs, :arg_type => Int, :default => [0, 1], :metavar => ["A", "B"]) ) return true end @test ap_settings10c(2) @aps_test_throws ap_settings10c(1) @aps_test_throws ap_settings10c(3) @aps_test_throws ap_settings10c('*') @aps_test_throws ap_settings10c('?') @aps_test_throws ap_settings10c('+') @aps_test_throws ap_settings10c('A') @aps_test_throws ap_settings10c('R') @aps_test_throws ap_settings10c('0') # Test to ensure multiple metavars cannot be used on positional args function ap_settings10d() s = ArgParseSettings(description = "Test 10 for ArgParse.jl", epilog = "Have fun!", version = "Version 1.0", add_version = true, exc_handler = ArgParse.debug_handler) add_arg_table!(s, "opt1", Dict( :nargs => 2, :arg_type => Int, :metavar => ["A", "B"]) ) return true end @aps_test_throws ap_settings10d() for s = [ap_settings10(), ap_settings10b()] ap_test10(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--opt1 A B] [-f] [-k] arg1 arg1 [arg2...] Test 10 for ArgParse.jl positional arguments: arg1 first argument, two entries at once arg2 second argument, eats up as many items as possible before an option (default: Any["no_arg_given"]) optional arguments: --opt1 A B an option (type: $Int, default: $([0, 1])) -f, --flag a flag -k, --karma increase karma Have fun! """ @test stringversion(s) == "Version 1.0\n" @ap_test_throws ap_test10([]) @test ap_test10(["X", "Y"]) == Dict{String,Any}("opt1"=>[0, 1], "flag"=>false, "karma"=>0, "arg1"=>Any["X", "Y"], "arg2"=>Any["no_arg_given"]) @test ap_test10(["X", "Y", "-k", "-f", "Z", "--karma", "--opt1", "2", "3"]) == Dict{String,Any}("opt1"=>[2, 3], "flag"=>true, "karma"=>2, "arg1"=>Any["X", "Y"], "arg2"=>Any["Z"]) @test ap_test10(["--opt1", "-3", "-5", "X", "Y", "-k", "-f", "Z", "--karma"]) == Dict{String,Any}("opt1"=>[-3, -5], "flag"=>true, "karma"=>2, "arg1"=>Any["X", "Y"], "arg2"=>Any["Z"]) @ap_test_throws ap_test10(["--opt"]) @ap_test_throws ap_test10(["--opt="]) @ap_test_throws ap_test10(["--opt", "", "X", "Y"]) @ap_test_throws ap_test10(["--opt", "1e-2", "X", "Y"]) @ap_test_throws ap_test10(["X", "Y", "--opt1", "1", "a"]) @ap_test_throws ap_test10(["X", "Y", "--opt1", "1"]) @ap_test_throws ap_test10(["X", "Y", "--opt1", "a", "b"]) @aps_test_throws @add_arg_table!(s, "required_arg_after_optional_args", required = true) # wrong default @aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, default = 1.5) # wrong range tester @aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, range_tester = x->string(x), default = 1) @aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, range_tester = x->sqrt(x)<1, default = -1) end end ================================================ FILE: test/argparse_test11.jl ================================================ @testset "test 11" begin ARGS = split("say hello --to world") s = ArgParseSettings() @add_arg_table! s begin "say" action = :command end @add_arg_table! s["say"] begin "what" help = "a positional argument" required = true "--to" help = "an option with an argument" end args = parse_args(ARGS, s, as_symbols=true) # make sure keys in args[:say] dict are of type Symbol # when as_symbols=true for (arg, val) in args[:say] @test typeof(arg) == Symbol end end ================================================ FILE: test/argparse_test12.jl ================================================ # test 12: mutually exclusive and required argument groups @testset "test 12" begin function ap_settings12() s = ArgParseSettings("Test 12 for ArgParse.jl", exc_handler = ArgParse.debug_handler) add_arg_group!(s, "mutually exclusive options", exclusive=true) @add_arg_table! s begin "--maybe", "-M" action = :store_true help = "maybe..." "--maybe-not", "-N" action = :store_true help = "maybe not..." end add_arg_group!(s, "required mutually exclusive options", "reqexc", exclusive=true, required=true) @add_arg_table! s begin "--either", "-E" action = :store_true help = "choose the `either` option" end add_arg_group!(s, "required arguments", required=true) @add_arg_table! s begin "--enhance", "-+" action = :store_true help = "set the enhancement option" "arg1" nargs = 2 # eats up two arguments; puts the result in a Vector help = "first argument, two " * "entries at once" end set_default_arg_group!(s, "reqexc") @add_arg_table! s begin "--or", "-O" action = :store_arg arg_type = Int default = 0 help = "set the `or` option" end set_default_arg_group!(s) @add_arg_table! s begin "-k" action = :store_const default = 0 constant = 42 help = "provide the answer" "--or-perhaps" action = :store_arg arg_type = String default = "" help = "set the `or-perhaps` option" group = "reqexc" end return s end let s = ap_settings12() ap_test12(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) {-E | -O OR | --or-perhaps OR-PERHAPS} [-M | -N] [-+] [-k] [arg1 arg1] Test 12 for ArgParse.jl optional arguments: -k provide the answer mutually exclusive options: -M, --maybe maybe... -N, --maybe-not maybe not... required mutually exclusive options: -E, --either choose the `either` option -O, --or OR set the `or` option (type: $(Int), default: 0) --or-perhaps OR-PERHAPS set the `or-perhaps` option (default: \"\") required arguments: -+, --enhance set the enhancement option arg1 first argument, two entries at once """ @ap_test_throws ap_test12([]) @test ap_test12(["-E", "-+"]) == Dict{String,Any}("k"=>0, "maybe"=>false, "maybe-not"=>false, "either"=>true, "or"=>0, "or-perhaps"=>"", "enhance"=>true, "arg1"=>[]) @test ap_test12(["-E", "-+", "--either"]) == Dict{String,Any}("k"=>0, "maybe"=>false, "maybe-not"=>false, "either"=>true, "or"=>0, "or-perhaps"=>"", "enhance"=>true, "arg1"=>[]) @test ap_test12(["A", "B", "--either"]) == Dict{String,Any}("k"=>0, "maybe"=>false, "maybe-not"=>false, "either"=>true, "or"=>0, "or-perhaps"=>"", "enhance"=>false, "arg1"=>["A", "B"]) @test ap_test12(["--enhance", "A", "B", "--or", "55", "-k"]) == Dict{String,Any}("k"=>42, "maybe"=>false, "maybe-not"=>false, "either"=>false, "or"=>55, "or-perhaps"=>"", "enhance"=>true, "arg1"=>["A", "B"]) @test ap_test12(["A", "B", "-Mk+O55", "-M"]) == Dict{String,Any}("k"=>42, "maybe"=>true, "maybe-not"=>false, "either"=>false, "or"=>55, "or-perhaps"=>"", "enhance"=>true, "arg1"=>["A", "B"]) @test ap_test12(["--enhance", "A", "B", "--or=55", "-k", "--maybe-not"]) == Dict{String,Any}("k"=>42, "maybe"=>false, "maybe-not"=>true, "either"=>false, "or"=>55, "or-perhaps"=>"", "enhance"=>true, "arg1"=>["A", "B"]) @test ap_test12(["--enhance", "A", "B", "--or-perhaps", "--either", "-k", "--maybe-not"]) == Dict{String,Any}("k"=>42, "maybe"=>false, "maybe-not"=>true, "either"=>false, "or"=>0, "or-perhaps"=>"--either", "enhance"=>true, "arg1"=>["A", "B"]) # combinations of missing arguments and too many arguments from the same group @ap_test_throws ap_test12(["-M", "--enhance", "A", "B", "--or", "55", "-k", "--maybe-not"]) @ap_test_throws ap_test12(["--maybe", "--enhance", "A", "B", "--or", "55", "-k", "-N"]) @ap_test_throws ap_test12(["--enhance", "A", "B", "-MkNO=55"]) @ap_test_throws ap_test12(["--maybe", "--enhance", "A", "B", "-N"]) @ap_test_throws ap_test12(["-+NE", "--or-perhaps=?"]) @ap_test_throws ap_test12(["-ME", "A", "A", "--or-perhaps=?"]) @ap_test_throws ap_test12(["-MO55", "A", "A", "--or-perhaps=?"]) @ap_test_throws ap_test12(["--enhanced", "-+MkO55", "A", "A", "--or-perhaps=?"]) # invalid arguments in mutually exclusive groups @aps_test_throws @add_arg_table!(s, "arg2", action = :store_arg, group = "reqexc") set_default_arg_group!(s, "reqexc") @aps_test_throws @add_arg_table!(s, "arg2", action = :store_arg) end end ================================================ FILE: test/argparse_test13.jl ================================================ # test 13: help_width and help_alignment_width settings @testset "test 13" begin function ap_settings13() s = ArgParseSettings(description = "Test 13 for ArgParse.jl. This description is made unnecessarily long for the sake of testing the help_text_width setting.", epilog = "This epilog is also made unnecessarily long for the same reason as the description, i.e., testing the help_text_width setting.", exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "--option1" arg_type = Int default = 0 help = "an option, not used for much really, " * "indeed it is not actually used for anything. " * "That is why its name is so undescriptive." "-O", "--long-option" arg_type = Symbol default = :xyz help = "another option, this time it has a fancy name " * "and yet it is still completely useless." "arg1" help = "first argument" required = true "arg2" default = "no arg2 given" help = "second argument" end return s end let s = ap_settings13() @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--option1 OPTION1] [-O LONG-OPTION] arg1 [arg2] Test 13 for ArgParse.jl. This description is made unnecessarily long for the sake of testing the help_text_width setting. positional arguments: arg1 first argument arg2 second argument (default: "no arg2 given") optional arguments: --option1 OPTION1 an option, not used for much really, indeed it is not actually used for anything. That is why its name is so undescriptive. (type: $Int, default: 0) -O, --long-option LONG-OPTION another option, this time it has a fancy name and yet it is still completely useless. (type: Symbol, default: :xyz) This epilog is also made unnecessarily long for the same reason as the description, i.e., testing the help_text_width setting. """ s.help_width = 120 @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--option1 OPTION1] [-O LONG-OPTION] arg1 [arg2] Test 13 for ArgParse.jl. This description is made unnecessarily long for the sake of testing the help_text_width setting. positional arguments: arg1 first argument arg2 second argument (default: "no arg2 given") optional arguments: --option1 OPTION1 an option, not used for much really, indeed it is not actually used for anything. That is why its name is so undescriptive. (type: $Int, default: 0) -O, --long-option LONG-OPTION another option, this time it has a fancy name and yet it is still completely useless. (type: Symbol, default: :xyz) This epilog is also made unnecessarily long for the same reason as the description, i.e., testing the help_text_width setting. """ s.help_width = 50 @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--option1 OPTION1] [-O LONG-OPTION] arg1 [arg2] Test 13 for ArgParse.jl. This description is made unnecessarily long for the sake of testing the help_text_width setting. positional arguments: arg1 first argument arg2 second argument (default: "no arg2 given") optional arguments: --option1 OPTION1 an option, not used for much really, indeed it is not actually used for anything. That is why its name is so undescriptive. (type: $Int, default: 0) -O, --long-option LONG-OPTION another option, this time it has a fancy name and yet it is still completely useless. (type: Symbol, default: :xyz) This epilog is also made unnecessarily long for the same reason as the description, i.e., testing the help_text_width setting. """ s.help_width = 100 s.help_alignment_width = 50 @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--option1 OPTION1] [-O LONG-OPTION] arg1 [arg2] Test 13 for ArgParse.jl. This description is made unnecessarily long for the sake of testing the help_text_width setting. positional arguments: arg1 first argument arg2 second argument (default: "no arg2 given") optional arguments: --option1 OPTION1 an option, not used for much really, indeed it is not actually used for anything. That is why its name is so undescriptive. (type: $Int, default: 0) -O, --long-option LONG-OPTION another option, this time it has a fancy name and yet it is still completely useless. (type: Symbol, default: :xyz) This epilog is also made unnecessarily long for the same reason as the description, i.e., testing the help_text_width setting. """ s.help_width = 50 s.help_alignment_width = 4 @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--option1 OPTION1] [-O LONG-OPTION] arg1 [arg2] Test 13 for ArgParse.jl. This description is made unnecessarily long for the sake of testing the help_text_width setting. positional arguments: arg1 first argument arg2 second argument (default: "no arg2 given") optional arguments: --option1 OPTION1 an option, not used for much really, indeed it is not actually used for anything. That is why its name is so undescriptive. (type: $Int, default: 0) -O, --long-option LONG-OPTION another option, this time it has a fancy name and yet it is still completely useless. (type: Symbol, default: :xyz) This epilog is also made unnecessarily long for the same reason as the description, i.e., testing the help_text_width setting. """ end end ================================================ FILE: test/argparse_test14.jl ================================================ # test 14: default values converted to arg_type @testset "test 14" begin function ap_settings14() s = ArgParseSettings(description = "Test 14 for ArgParse.jl", exc_handler = ArgParse.debug_handler) @add_arg_table! s begin "--opt1" nargs = '?' arg_type = Int default = 0.0 constant = 1.0 help = "an option" "-O" arg_type = Symbol default = "xyz" help = "another option" "--opt2" nargs = '+' arg_type = Int default = [0.0] help = "another option, many args" "--opt3" action = :append_arg arg_type = Int default = [0.0] help = "another option, appends arg" "--opt4" action = :append_arg nargs = '+' arg_type = Int default = [[0.0]] help = "another option, appends many args" end return s end let s = ap_settings14() ap_test14(args) = parse_args(args, s) @test stringhelp(s) == """ usage: $(basename(Base.source_path())) [--opt1 [OPT1]] [-O O] [--opt2 OPT2 [OPT2...]] [--opt3 OPT3] [--opt4 OPT4 [OPT4...]] Test 14 for ArgParse.jl optional arguments: --opt1 [OPT1] an option (type: $(Int), default: 0, without arg: 1) -O O another option (type: Symbol, default: :xyz) --opt2 OPT2 [OPT2...] another option, many args (type: $(Int), default: [0]) --opt3 OPT3 another option, appends arg (type: $(Int), default: [0]) --opt4 OPT4 [OPT4...] another option, appends many args (type: $(Int), default: $([[0]])) """ @test ap_test14([]) == Dict{String,Any}("opt1"=>0, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0], "opt4"=>[[0]]) @test ap_test14(["--opt1"]) == Dict{String,Any}("opt1"=>1, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0], "opt4"=>[[0]]) @test ap_test14(["--opt1", "33", "--opt2", "5", "7"]) == Dict{String,Any}("opt1"=>33, "O"=>:xyz, "opt2"=>[5, 7], "opt3"=>[0], "opt4"=>[[0]]) @test ap_test14(["--opt3", "5", "--opt3", "7"]) == Dict{String,Any}("opt1"=>0, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0, 5, 7], "opt4"=>[[0]]) @test ap_test14(["--opt4", "5", "7", "--opt4", "11", "13", "17"]) == Dict{String,Any}("opt1"=>0, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0], "opt4"=>[[0], [5, 7], [11, 13, 17]]) end end ================================================ FILE: test/args-file1 ================================================ --opt2=y ================================================ FILE: test/args-file2 ================================================ @args-file1 --opt1=x ================================================ FILE: test/common.jl ================================================ using ArgParse using Test macro ap_test_throws(args) :(@test_throws ArgParseError $(esc(args))) end macro aps_test_throws(args) :(@test_throws ArgParseSettingsError $(esc(args))) end macro noout_test(args) quote mktemp() do _,io redirect_stdout(io) do @test $(esc(args)) end end end end macro test_addtable_failure(ex...) ex = [nothing, ex...] ex = Expr(:call, :macroexpand, @__MODULE__, Expr(:quote, Expr(:macrocall, Symbol("@add_arg_table!"), ex...))) err = @static VERSION ≥ v"1.7.0-DEV.937" ? ArgParseSettingsError : LoadError quote @test_throws $err $ex end end macro tostring(ex) @assert ex.head == :call f = esc(ex.args[1]) a = map(esc, ex.args[2:end]) newcall = Expr(:call, f, :io, a...) quote io = IOBuffer() $newcall String(take!(io)) end end stringhelp(s::ArgParseSettings) = @tostring ArgParse.show_help(s, exit_when_done = false) stringversion(s::ArgParseSettings) = @tostring ArgParse.show_version(s, exit_when_done = false) ================================================ FILE: test/runtests.jl ================================================ module ArgParseTests include("common.jl") for i = 1:14 try s_i = lpad(string(i), 2, "0") include("argparse_test$s_i.jl") catch err println() rethrow(err) end end println() end