Showing preview only (243K chars total). Download the full file or copy to clipboard to get everything.
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 <carlobaldassi@gmail.com>"]
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: <PROGRAM> [-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) ? "<PROGRAM>" : 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) ? "<PROGRAM>" : 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: <command> [--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
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
Condensed preview — 50 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (249K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 256,
"preview": "# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\nversion: 2\nupda"
},
{
"path": ".github/workflows/TagBot.yml",
"chars": 362,
"preview": "name: TagBot\non:\n issue_comment:\n types:\n - created\n workflow_dispatch:\njobs:\n TagBot:\n if: github.event_n"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1344,
"preview": "name: CI\non:\n push:\n branches:\n - master\n tags: '*'\n pull_request:\n schedule:\n # Run CI against `master"
},
{
"path": ".github/workflows/documentation.yml",
"chars": 902,
"preview": "name: Documentation\n\non:\n push:\n branches:\n - master\n tags: '*'\n pull_request:\n\njobs:\n build:\n permissi"
},
{
"path": "LICENSE.md",
"chars": 3895,
"preview": "The ArgParse Julia module is licensed under the MIT License:\n\n> Copyright (c) 2012: Carlo Baldassi\n>\n> Permission is her"
},
{
"path": "Project.toml",
"chars": 403,
"preview": "name = \"ArgParse\"\nuuid = \"c7e460c6-2fb9-53a9-8c5b-16f535851c63\"\nauthor = [\"Carlo Baldassi <carlobaldassi@gmail.com>\"]\nve"
},
{
"path": "README.md",
"chars": 5118,
"preview": "# ArgParse.jl\n\n[![DOCS][docs-img]][docs-url] [![CI][CI-img]][CI-url] [![CODECOV][codecov-img]][codecov-url]\n\nArgParse.jl"
},
{
"path": "docs/.gitignore",
"chars": 21,
"preview": "Manifest.toml\nbuild/\n"
},
{
"path": "docs/Project.toml",
"chars": 136,
"preview": "[deps]\nArgParse = \"c7e460c6-2fb9-53a9-8c5b-16f535851c63\"\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\n\n[compat]\nD"
},
{
"path": "docs/make.jl",
"chars": 576,
"preview": "using Documenter, ArgParse\n\nCIbuild = get(ENV, \"CI\", nothing) == \"true\"\n\nmakedocs(\n modules = [ArgParse],\n format"
},
{
"path": "docs/src/arg_table.md",
"chars": 19322,
"preview": "# Argument table\n\nThe argument table is used to store allowed arguments and options in an [`ArgParseSettings`](@ref) obj"
},
{
"path": "docs/src/conflicts.md",
"chars": 1873,
"preview": "# Conflicts and overrides\n\nConflicts between arguments, be them options, positional arguments or commands, can arise for"
},
{
"path": "docs/src/custom.md",
"chars": 1018,
"preview": "# Parsing to custom types\n\nIf you specify an `arg_type` setting (see the [Argument entry settings](@ref) section) for an"
},
{
"path": "docs/src/details.md",
"chars": 2227,
"preview": "# Parsing details\n\nDuring parsing, `parse_args` must determine whether an argument is an option, an option argument, a p"
},
{
"path": "docs/src/import.md",
"chars": 212,
"preview": "# Importing settings\n\nIt may be useful in some cases to import an argument table into the one which is to be used, for e"
},
{
"path": "docs/src/index.md",
"chars": 6158,
"preview": "# ArgParse.jl documentation\n\n```@meta\nCurrentModule = ArgParse\n```\n\nThis [Julia](http://julialang.org) package allows th"
},
{
"path": "docs/src/parse_args.md",
"chars": 54,
"preview": "# The `parse_args` function\n\n```@docs\nparse_args\n```\n\n"
},
{
"path": "docs/src/settings.md",
"chars": 59,
"preview": "# Settings\n\n```@docs\nArgParseSettings\n@project_version\n```\n"
},
{
"path": "examples/argparse_example1.jl",
"chars": 682,
"preview": "# example 1: minimal options/arguments, auto-generated help/version\n\nusing ArgParse\n\nfunction main(args)\n\n # initiali"
},
{
"path": "examples/argparse_example2.jl",
"chars": 774,
"preview": "# example 2: add some flags and the help lines for options\n\nusing ArgParse\n\nfunction main(args)\n\n s = ArgParseSetting"
},
{
"path": "examples/argparse_example3.jl",
"chars": 1910,
"preview": "# example 3: version information, default values, options with\n# types and variable number of arguments\n\nusin"
},
{
"path": "examples/argparse_example4.jl",
"chars": 3241,
"preview": "# example 4: dest_name, metavar, range_tester, alternative\n# actions, epilog with examples\n\nusing ArgParse\n\nf"
},
{
"path": "examples/argparse_example5.jl",
"chars": 1994,
"preview": "# example 5: manual help/version, import another parser\n\nusing ArgParse\n\nfunction main(args)\n\n s0 = ArgParseSettings("
},
{
"path": "examples/argparse_example6.jl",
"chars": 2688,
"preview": "# example 6: commands & subtables\n\nusing ArgParse\n\nfunction main(args)\n\n s = ArgParseSettings(\"Example 6 for argparse"
},
{
"path": "examples/argparse_example7.jl",
"chars": 2223,
"preview": "# example 7: argument groups\n\nusing ArgParse\n\nfunction main(args)\n\n s = ArgParseSettings(\"Example 7 for argparse.jl: "
},
{
"path": "examples/argparse_example8.jl",
"chars": 1475,
"preview": "# example 8: mutually exculsive and required groups\n\nusing ArgParse\n\nfunction main(args)\n\n s = ArgParseSettings(\"Exam"
},
{
"path": "src/ArgParse.jl",
"chars": 1175,
"preview": "\"\"\"\n ArgParse\n\nThis module allows the creation of user-friendly command-line interfaces to Julia programs:\nthe progra"
},
{
"path": "src/common.jl",
"chars": 1206,
"preview": "## Some common functions, constants, macros\n\n# auxiliary functions/constants\nfound_a_bug() = error(\"you just found a bug"
},
{
"path": "src/deprecated.jl",
"chars": 1401,
"preview": "\n@deprecate add_arg_table add_arg_table!\n@deprecate import_settings(settings, other) import_settings!(settings, other)\n@"
},
{
"path": "src/parsing.jl",
"chars": 34269,
"preview": "## All types, functions and constants related to the actual process of\n## parsing the arguments\n\n# ArgParseError\nstruct "
},
{
"path": "src/settings.jl",
"chars": 59409,
"preview": "## All types, functions and constants related to the specification of the arguments\n\n# actions\nconst all_actions = [:sto"
},
{
"path": "test/Project.toml",
"chars": 72,
"preview": "version = \"1.0.0\"\n\n[deps]\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n"
},
{
"path": "test/argparse_test01.jl",
"chars": 2583,
"preview": "# test 01: minimal options/arguments, auto-generated help/version;\n# function version of add_arg_table!\n\n@tests"
},
{
"path": "test/argparse_test02.jl",
"chars": 11941,
"preview": "# test 02: version information, default values, flags,\n# options with types, optional arguments, variable\n# "
},
{
"path": "test/argparse_test03.jl",
"chars": 8501,
"preview": "# test 03: dest_name, metavar, range_tester, alternative\n# actions, custom parser\n\nstruct CustomType\nend\n\n@test"
},
{
"path": "test/argparse_test04.jl",
"chars": 5660,
"preview": "# test 04: manual help/version, import another parser\n\n@testset \"test 04\" begin\n\nfunction ap_settings4()\n\n s0 = ArgPa"
},
{
"path": "test/argparse_test05.jl",
"chars": 14687,
"preview": "# test 05: commands & subtables\n\n@testset \"test 05\" begin\n\nfunction ap_settings5()\n\n s = ArgParseSettings(\"Test 5 for"
},
{
"path": "test/argparse_test06.jl",
"chars": 4237,
"preview": "# test 06: argument groups\n\n@testset \"test 06\" begin\n\nfunction ap_settings6()\n\n s = ArgParseSettings(\"Test 6 for ArgP"
},
{
"path": "test/argparse_test07.jl",
"chars": 2163,
"preview": "# test 07: required options, line breaks in desc/epilog\n\n@testset \"test 07\" begin\n\nfunction ap_settings7()\n\n s = ArgP"
},
{
"path": "test/argparse_test08.jl",
"chars": 2588,
"preview": "# test 08: read args from file, read version from project\n\n@testset \"test 08\" begin\n\nfunction ap_settings8a()\n\n s = A"
},
{
"path": "test/argparse_test09.jl",
"chars": 1722,
"preview": "# test 09: preformatted desc/epilog\n\n@testset \"test 09\" begin\n\nfunction ap_settings9()\n\n s = ArgParseSettings(descrip"
},
{
"path": "test/argparse_test10.jl",
"chars": 7125,
"preview": "# test 10: multiple metavars\n# function version of add_arg_table!\n\n@testset \"test 10\" begin\n\nfunction ap_settin"
},
{
"path": "test/argparse_test11.jl",
"chars": 587,
"preview": "@testset \"test 11\" begin\n\n ARGS = split(\"say hello --to world\")\n\n s = ArgParseSettings()\n\n @add_arg_table! s be"
},
{
"path": "test/argparse_test12.jl",
"chars": 5193,
"preview": "# test 12: mutually exclusive and required argument groups\n\n@testset \"test 12\" begin\n\nfunction ap_settings12()\n\n s = "
},
{
"path": "test/argparse_test13.jl",
"chars": 7113,
"preview": "# test 13: help_width and help_alignment_width settings\n\n@testset \"test 13\" begin\n\nfunction ap_settings13()\n\n s = Arg"
},
{
"path": "test/argparse_test14.jl",
"chars": 2720,
"preview": "# test 14: default values converted to arg_type\n\n@testset \"test 14\" begin\n\nfunction ap_settings14()\n\n s = ArgParseSet"
},
{
"path": "test/args-file1",
"chars": 9,
"preview": "--opt2=y\n"
},
{
"path": "test/args-file2",
"chars": 21,
"preview": "@args-file1\n--opt1=x\n"
},
{
"path": "test/common.jl",
"chars": 1093,
"preview": "using ArgParse\nusing Test\n\nmacro ap_test_throws(args)\n :(@test_throws ArgParseError $(esc(args)))\nend\n\nmacro aps_test"
},
{
"path": "test/runtests.jl",
"chars": 223,
"preview": "module ArgParseTests\n\ninclude(\"common.jl\")\n\nfor i = 1:14\n try\n s_i = lpad(string(i), 2, \"0\")\n include(\""
}
]
About this extraction
This page contains the full source code of the carlobaldassi/ArgParse.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 50 files (229.1 KB), approximately 60.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.