Merged clap_derive using git-subtree

This commit is contained in:
Pavan Kumar Sunkara 2020-01-18 17:40:07 +05:30
commit 5e8f424dfe
133 changed files with 7317 additions and 0 deletions

13
clap_derive/.clog.toml Normal file
View file

@ -0,0 +1,13 @@
[clog]
repository = "https://github.com/kbknapp/clap_derive"
outfile = "CHANGELOG.md"
from-latest-tag = true
[sections]
Performance = ["perf"]
Improvements = ["impr", "im", "imp"]
Documentation = ["docs"]
Deprecations = ["depr"]
Examples = ["examples"]
"New Settings" = ["setting", "settings"]
"API Additions" = ["add", "api"]

84
clap_derive/.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,84 @@
# How to Contribute
Contributions are always welcome! And there is a multitude of ways in which you can help depending on what you like to do, or are good at. Anything from documentation, code cleanup, issue completion, new features, you name it, even filing issues is contributing and greatly appreciated!
Another really great way to help is if you find an interesting, or helpful way in which to use `clap_derive`. You can either add it to the [examples/](examples) directory, or file an issue and tell me. I'm all about giving credit where credit is due :)
### Testing Code
To test with all features both enabled and disabled, you can run these commands:
```sh
$ cargo test
```
Alternatively, if you have [`just`](https://github.com/casey/just) installed you can run the prebuilt recipes. *Not* using `just` is perfectly fine as well, it simply bundles commands automatically.
For example, to test the code, as above simply run:
```sh
$ just run-tests
```
From here on, I will list the appropriate `cargo` command as well as the `just` command.
Sometimes it's helpful to only run a subset of the tests, which can be done via:
```sh
$ cargo test --test <test_name>
# Or
$ just run-test <test_name>
```
### Linting Code
During the CI process `clap_derive` runs against many different lints using [`clippy`](https://github.com/Manishearth/rust-clippy). In order to check if these lints pass on your own computer prior to submitting a PR you'll need a nightly compiler.
In order to check the code for lints run either:
```sh
$ cargo +nightly build --features lints
# Or
$ just lint
```
### Commit Messages
I use a [conventional](https://github.com/ajoslin/conventional-changelog/blob/a5505865ff3dd710cf757f50530e73ef0ca641da/conventions/angular.md) changelog format so I can update my changelog automatically using [clog](https://github.com/clog-tool/clog-cli)
* Please format your commit subject line using the following format: `TYPE(COMPONENT): MESSAGE` where `TYPE` is one of the following:
- `api` - An addition to the API
- `setting` - A new `AppSettings` variant
- `feat` - A new feature of an existing API
- `imp` - An improvement to an existing feature/API
- `perf` - A performance improvement
- `docs` - Changes to documentation only
- `tests` - Changes to the testing framework or tests only
- `fix` - A bug fix
- `refactor` - Code functionality doesn't change, but underlying structure may
- `style` - Stylistic changes only, no functionality changes
- `wip` - A work in progress commit (Should typically be `git rebase`'ed away)
- `chore` - Catch all or things that have to do with the build system, etc
- `examples` - Changes to existing example, or a new example
* The `COMPONENT` is optional, and may be a single file, directory, or logical component. Parenthesis can be omitted if you are opting not to use the `COMPONENT`.
### Tests and Documentation
1. Create tests for your changes
2. **Ensure the tests are passing.** Run the tests (`cargo test`), alternatively `just run-tests` if you have `just` installed.
3. **Optional** Run the lints (`cargo +nightly build --features lints`) (requires a nightly compiler), alternatively `just lint`
4. Ensure your changes contain documentation if adding new APIs or features.
### Preparing the PR
1. `git rebase` into concise commits and remove `--fixup`s or `wip` commits (`git rebase -i HEAD~NUM` where `NUM` is number of commits back to start the rebase)
2. Push your changes back to your fork (`git push origin $your-branch`)
3. Create a pull request against `master`! (You can also create the pull request first, and we'll merge when ready. This a good way to discuss proposed changes.)
### Other ways to contribute
Another really great way to help is if you find an interesting, or helpful way in which to use `clap_derive`. You can either add it to the [examples/](../examples) directory, or file an issue and tell me. I'm all about giving credit where credit is due :)

23
clap_derive/.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,23 @@
<!--
Please use the following template to assist with creating an issue and to ensure a speedy resolution. If an area is not applicable, feel free to delete the area or mark with `N/A`
-->
### Rust Version
* Use the output of `rustc -V`
### Affected Version of clap and clap_derive
* Can be found in Cargo.lock of your project (i.e. `grep clap Cargo.lock`)
### Expected Behavior Summary
### Actual Behavior Summary
### Steps to Reproduce the issue
### Sample Code or Link to Sample Code

28
clap_derive/.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Compiled files
*.o
*.so
*.rlib
*.dll
# Executables
*.exe
# Generated by Cargo
/target/
/clap-test/target/
# Cargo files
Cargo.lock
# Temp files
.*~
# Backup files
*.bak
*.bk
*.orig
# Project files
.vscode/*
.idea/*
clap-rs.iml

156
clap_derive/CHANGELOG.md Normal file
View file

@ -0,0 +1,156 @@
# v0.2.10 (2018-06-07)
* 1.21.0 is the minimum required rustc version by
[@TeXitoi](https://github.com/TeXitoi)
# v0.2.9 (2018-06-05)
* Fix a bug when using `flatten` by
[@fbenkstein](https://github.com/fbenkstein)
* Update syn, quote and proc_macro2 by
[@TeXitoi](https://github.com/TeXitoi)
* Fix a regression when there is multiple authors by
[@windwardly](https://github.com/windwardly)
# v0.2.8 (2018-04-28)
* Add `StructOpt::from_iter_safe()`, which returns an `Error` instead of
killing the program when it fails to parse, or parses one of the
short-circuiting flags. ([#98](https://github.com/TeXitoi/structopt/pull/98)
by [@quodlibetor](https://github.com/quodlibetor))
* Allow users to enable `clap` features independently by
[@Kerollmops](https://github.com/Kerollmops)
* Fix a bug when flattening an enum
([#103](https://github.com/TeXitoi/structopt/pull/103) by
[@TeXitoi](https://github.com/TeXitoi)
# v0.2.7 (2018-04-12)
* Add flattening, the insertion of options of another StructOpt struct
into another ([#92](https://github.com/TeXitoi/structopt/pull/92))
by [@birkenfeld](https://github.com/birkenfeld)
* Fail compilation when using `default_value` or `required` with
`Option` ([#88](https://github.com/TeXitoi/structopt/pull/88)) by
[@Kerollmops](https://github.com/Kerollmops)
# v0.2.6 (2018-03-31)
* Fail compilation when using `default_value` or `required` with `bool` ([#80](https://github.com/TeXitoi/structopt/issues/80)) by [@TeXitoi](https://github.com/TeXitoi)
* Fix compilation with `#[deny(warnings)]` with the `!` type (https://github.com/rust-lang/rust/pull/49039#issuecomment-376398999) by [@TeXitoi](https://github.com/TeXitoi)
* Improve first example in the documentation ([#82](https://github.com/TeXitoi/structopt/issues/82)) by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.5 (2018-03-07)
* Work around breakage when `proc-macro2`'s nightly feature is enabled. ([#77](https://github.com/Texitoi/structopt/pull/77) and [proc-macro2#67](https://github.com/alexcrichton/proc-macro2/issues/67)) by [@fitzgen](https://github.com/fitzgen)
# v0.2.4 (2018-02-25)
* Fix compilation with `#![deny(missig_docs]` ([#74](https://github.com/TeXitoi/structopt/issues/74)) by [@TeXitoi](https://github.com/TeXitoi)
* Fix [#76](https://github.com/TeXitoi/structopt/issues/76) by [@TeXitoi](https://github.com/TeXitoi)
* Re-licensed to Apache-2.0/MIT by [@CAD97](https://github.com/cad97)
# v0.2.3 (2018-02-16)
* An empty line in a doc comment will result in a double linefeed in the generated about/help call by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.2 (2018-02-12)
* Fix [#66](https://github.com/TeXitoi/structopt/issues/66) by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.1 (2018-02-11)
* Fix a bug around enum tuple and the about message in the global help by [@TeXitoi](https://github.com/TeXitoi)
* Fix [#65](https://github.com/TeXitoi/structopt/issues/65) by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.0 (2018-02-10)
## Breaking changes
### Don't special case `u64` by [@SergioBenitez](https://github.com/SergioBenitez)
If you are using a `u64` in your struct to get the number of occurence of a flag, you should now add `parse(from_occurrences)` on the flag.
For example
```rust
#[clap(short = "v", long = "verbose")]
verbose: u64,
```
must be changed by
```rust
#[clap(short = "v", long = "verbose", parse(from_occurrences))]
verbose: u64,
```
This feature was surprising as shown in [#30](https://github.com/TeXitoi/structopt/issues/30). Using the `parse` feature seems much more natural.
### Change the signature of `Structopt::from_clap` to take its argument by reference by [@TeXitoi](https://github.com/TeXitoi)
There was no reason to take the argument by value. Most of the StructOpt users will not be impacted by this change. If you are using `StructOpt::from_clap`, just add a `&` before the argument.
### Fail if attributes are not used by [@TeXitoi](https://github.com/TeXitoi)
StructOpt was quite fuzzy in its attribute parsing: it was only searching for interresting things, e. g. something like `#[clap(foo(bar))]` was accepted but not used. It now fails the compilation.
You should have nothing to do here. This breaking change may highlight some missuse that can be bugs.
In future versions, if there is cases that are not highlighed, they will be considerated as bugs, not breaking changes.
### Use `raw()` wrapping instead of `_raw` suffixing by [@TeXitoi](https://github.com/TeXitoi)
The syntax of raw attributes is changed to improve the syntax.
You have to change `foo_raw = "bar", baz_raw = "foo"` by `raw(foo = "bar", baz = "foo")` or `raw(foo = "bar"), raw(baz = "foo")`.
## New features
* Add `parse(from_occurrences)` parser by [@SergioBenitez](https://github.com/SergioBenitez)
* Support 1-uple enum variant as subcommand by [@TeXitoi](https://github.com/TeXitoi)
* structopt-derive crate is now an implementation detail, structopt reexport the custom derive macro by [@TeXitoi](https://github.com/TeXitoi)
* Add the `StructOpt::from_iter` method by [@Kerollmops](https://github.com/Kerollmops)
## Documentation
* Improve doc by [@bestouff](https://github.com/bestouff)
* All the documentation is now on the structopt crate by [@TeXitoi](https://github.com/TeXitoi)
# v0.1.7 (2018-01-23)
* Allow opting out of clap default features by [@ski-csis](https://github.com/ski-csis)
# v0.1.6 (2017-11-25)
* Improve documentation by [@TeXitoi](https://github.com/TeXitoi)
* Fix bug [#31](https://github.com/TeXitoi/structopt/issues/31) by [@TeXitoi](https://github.com/TeXitoi)
# v0.1.5 (2017-11-14)
* Fix a bug with optional subsubcommand and Enum by [@TeXitoi](https://github.com/TeXitoi)
# v0.1.4 (2017-11-09)
* Implement custom string parser from either `&str` or `&OsStr` by [@kennytm](https://github.com/kennytm)
# v0.1.3 (2017-11-01)
* Improve doc by [@TeXitoi](https://github.com/TeXitoi)
# v0.1.2 (2017-11-01)
* Fix bugs [#24](https://github.com/TeXitoi/structopt/issues/24) and [#25](https://github.com/TeXitoi/structopt/issues/25) by [@TeXitoi](https://github.com/TeXitoi)
* Support of methods with something else that a string as argument thanks to `_raw` suffix by [@Flakebi](https://github.com/Flakebi)
# v0.1.1 (2017-09-22)
* Better formating of multiple authors by [@killercup](https://github.com/killercup)
# v0.1.0 (2017-07-17)
* Subcommand support by [@williamyaoh](https://github.com/williamyaoh)
# v0.0.5 (2017-06-16)
* Using doc comment to populate help by [@killercup](https://github.com/killercup)
# v0.0.3 (2017-02-11)
* First version with flags, arguments and options support by [@TeXitoi](https://github.com/TeXitoi)

View file

@ -0,0 +1,19 @@
# Contributors to `clap_derive`
| [<img alt="TeXitoi" src="https://avatars0.githubusercontent.com/u/5787066?v=4&s=117" width="117">](https://github.com/TeXitoi) | [<img alt="williamyaoh" src="https://avatars1.githubusercontent.com/u/5668019?v=4&s=117" width="117">](https://github.com/williamyaoh) | [<img alt="kbknapp" src="https://avatars1.githubusercontent.com/u/6942134?v=4&s=117" width="117">](https://github.com/kbknapp) | [<img alt="killercup" src="https://avatars1.githubusercontent.com/u/20063?v=4&s=117" width="117">](https://github.com/killercup) | [<img alt="Kerollmops" src="https://avatars0.githubusercontent.com/u/3610253?v=4&s=117" width="117">](https://github.com/Kerollmops) | [<img alt="Hoverbear" src="https://avatars3.githubusercontent.com/u/130903?v=4&s=117" width="117">](https://github.com/Hoverbear) |
| :----------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------: |
| [TeXitoi](https://github.com/TeXitoi) | [williamyaoh](https://github.com/williamyaoh) | [kbknapp](https://github.com/kbknapp) | [killercup](https://github.com/killercup) | [Kerollmops](https://github.com/Kerollmops) | [Hoverbear](https://github.com/Hoverbear) |
| [<img alt="SergioBenitez" src="https://avatars3.githubusercontent.com/u/1480321?v=4&s=117" width="117">](https://github.com/SergioBenitez) | [<img alt="quodlibetor" src="https://avatars3.githubusercontent.com/u/277161?v=4&s=117" width="117">](https://github.com/quodlibetor) | [<img alt="bruceadams" src="https://avatars0.githubusercontent.com/u/225823?v=4&s=117" width="117">](https://github.com/bruceadams) | [<img alt="CAD97" src="https://avatars1.githubusercontent.com/u/5992217?v=4&s=117" width="117">](https://github.com/CAD97) | [<img alt="fbenkstein" src="https://avatars2.githubusercontent.com/u/7721507?v=4&s=117" width="117">](https://github.com/fbenkstein) | [<img alt="windwardly" src="https://avatars3.githubusercontent.com/u/24761347?v=4&s=117" width="117">](https://github.com/windwardly) |
| :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
| [SergioBenitez](https://github.com/SergioBenitez) | [quodlibetor](https://github.com/quodlibetor) | [bruceadams](https://github.com/bruceadams) | [CAD97](https://github.com/CAD97) | [fbenkstein](https://github.com/fbenkstein) | [windwardly](https://github.com/windwardly) |
| [<img alt="fitzgen" src="https://avatars2.githubusercontent.com/u/74571?v=4&s=117" width="117">](https://github.com/fitzgen) | [<img alt="Flakebi" src="https://avatars2.githubusercontent.com/u/6499211?v=4&s=117" width="117">](https://github.com/Flakebi) | [<img alt="ski-csis" src="https://avatars1.githubusercontent.com/u/1162493?v=4&s=117" width="117">](https://github.com/ski-csis) | [<img alt="tvincent2" src="https://avatars0.githubusercontent.com/u/2185278?v=4&s=117" width="117">](https://github.com/tvincent2) | [<img alt="tshepang" src="https://avatars0.githubusercontent.com/u/588486?v=4&s=117" width="117">](https://github.com/tshepang) | [<img alt="bestouff" src="https://avatars0.githubusercontent.com/u/1163188?v=4&s=117" width="117">](https://github.com/bestouff) |
| :--------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: |
| [fitzgen](https://github.com/fitzgen) | [Flakebi](https://github.com/Flakebi) | [ski-csis](https://github.com/ski-csis) | [tvincent2](https://github.com/tvincent2) | [tshepang](https://github.com/tshepang) | [bestouff](https://github.com/bestouff) |
| [<img alt="hcpl" src="https://avatars3.githubusercontent.com/u/17436405?v=4&s=117" width="117">](https://github.com/hcpl) | [<img alt="kennytm" src="https://avatars1.githubusercontent.com/u/103023?v=4&s=117" width="117">](https://github.com/kennytm) | [<img alt="spease" src="https://avatars1.githubusercontent.com/u/2825204?v=4&s=117" width="117">](https://github.com/spease) | [<img alt="birkenfeld" src="https://avatars0.githubusercontent.com/u/144359?v=4&s=117" width="117">](https://github.com/birkenfeld) |
| :-----------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: |
| [hcpl](https://github.com/hcpl) | [kennytm](https://github.com/kennytm) | [spease](https://github.com/spease) | [birkenfeld](https://github.com/birkenfeld) |
This list was generated by [mgechev/github-contributors-list](https://github.com/mgechev/github-contributors-list)

42
clap_derive/Cargo.toml Normal file
View file

@ -0,0 +1,42 @@
[package]
name = "clap_derive"
version = "0.3.0"
edition = "2018"
authors = [
"Guillaume Pinot <texitoi@texitoi.eu>",
"Kevin K. <kbknapp@gmail.com>",
"hoverbear <andrew@hoverbear.org>"
]
description = "Parse command line argument by defining a struct, derive crate."
documentation = "https://docs.rs/clap_derive"
repository = "https://github.com/clap-rs/clap_derive"
keywords = ["clap", "cli", "derive", "proc_macro", "parse"]
categories = ["command-line-interface", "development-tools::procedural-macro-helpers"]
license = "Apache-2.0/MIT"
readme = "README.md"
[lib]
proc-macro = true
[badges]
travis-ci = { repository = "clap-rs/clap_derive" }
appveyor = { repository = "https://github.com/clap-rs/clap_derive", service = "github" }
[dependencies]
syn = { version = "1", features = ["full"] }
quote = "1"
proc-macro2 = "1"
heck = "0.3.0"
proc-macro-error = "0.4.3"
[dev-dependencies]
clap = { git = "https://github.com/clap-rs/clap", branch = "master"} # ONLY FOR INITIAL DEVELOPMENT...change to real crates.io ver for rlease!
trybuild = "1.0.5"
rustversion = "0.1"
[features]
default = []
nightly = []
lints = []
debug = []
doc = []

201
clap_derive/LICENSE-APACHE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
clap_derive/LICENSE-MIT Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
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.

169
clap_derive/README.md Normal file
View file

@ -0,0 +1,169 @@
# Work in Progress
This crate is currently a work in progress and not meant to be used. Please use [`structopt`](https://github.com/TeXitoi/structopt)
while this crate is being built.
# clap_derive[![Build status](https://travis-ci.org/clap-rs/clap_derive.svg?branch=master)](https://travis-ci.org/clap-rs/clap_derive) [![](https://img.shields.io/crates/v/clap_derive.svg)](https://crates.io/crates/clap_derive) [![](https://docs.rs/clap_derive/badge.svg)](https://docs.rs/clap_derive)
Parse command line argument by defining a struct. It combines [structopt](https://github.com/TeXitoi/structopt) and [clap](https://crates.io/crates/clap) into a single experience. This crate is used by clap, and not meant to be used directly by
consumers.
## Documentation
Find it on [Docs.rs](https://docs.rs/clap_derive). You can also check the [examples](https://github.com/clap-rs/clap_derive/tree/master/examples) and the [changelog](https://github.com/clap-rs/clap_derive/blob/master/CHANGELOG.md).
## Example
Add `clap` to your dependencies of your `Cargo.toml`:
```toml
[dependencies]
clap = "3"
```
And then, in your rust file:
```rust
#[macro_use]
extern crate clap;
use std::path::PathBuf;
use clap::Clap;
/// A basic example
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
// A flag, true if used in the command line. Note doc comment will
// be used for the help message of the flag. The name of the
// argument will be, by default, based on the name of the field.
/// Activate debug mode
#[clap(short, long)]
debug: bool,
// The number of occurrences of the `v/verbose` flag
/// Verbose mode (-v, -vv, -vvv, etc.)
#[clap(short, long, parse(from_occurrences))]
verbose: u8,
/// Set speed
#[clap(short, long, default_value = "42")]
speed: f64,
/// Output file
#[clap(short, long, parse(from_os_str))]
output: PathBuf,
// the long option will be translated by default to kebab case,
// i.e. `--nb-cars`.
/// Number of cars
#[clap(short = "c", long)]
nb_cars: Option<i32>,
/// admin_level to consider
#[clap(short, long)]
level: Vec<String>,
/// Files to process
#[clap(name = "FILE", parse(from_os_str))]
files: Vec<PathBuf>,
}
fn main() {
let opt = Opt::parse();
println!("{:#?}", opt);
}
```
Using this example:
```
$ ./basic
error: The following required arguments were not provided:
--output <output>
USAGE:
basic --output <output> --speed <speed>
For more information try --help
$ ./basic --help
basic 0.3.0
Guillaume Pinot <texitoi@texitoi.eu>, others
A basic example
USAGE:
basic [FLAGS] [OPTIONS] --output <output> [--] [file]...
ARGS:
<FILE>... Files to process
FLAGS:
-d, --debug Activate debug mode
-h, --help Prints help information
-V, --version Prints version information
-v, --verbose Verbose mode (-v, -vv, -vvv, etc.)
OPTIONS:
-l, --level <level>... admin_level to consider
-c, --nb-cars <nb-cars> Number of cars
-o, --output <output> Output file
-s, --speed <speed> Set speed [default: 42]
ARGS:
<file>... Files to process
$ ./basic -o foo.txt
Opt {
debug: false,
verbose: 0,
speed: 42.0,
output: "foo.txt",
nb_cars: None,
level: [],
files: [],
}
$ ./basic -o foo.txt -dvvvs 1337 -l alice -l bob --nb-cars 4 bar.txt baz.txt
Opt {
debug: true,
verbose: 3,
speed: 1337.0,
output: "foo.txt",
nb_cars: Some(
4,
),
level: [
"alice",
"bob",
],
files: [
"bar.txt",
"baz.txt",
],
}
```
## clap_derive rustc version policy
- Minimum rustc version modification must be specified in the [changelog](https://github.com/clap-rs/clap_derive/blob/master/CHANGELOG.md) and in the [travis configuration](https://github.com/clap-rs/clap_derive/blob/master/.travis.yml).
- Contributors can increment minimum rustc version without any justification if the new version is required by the latest version of one of clap_derive's depedencies (`cargo update` will not fail on clap_derive).
- Contributors can increment minimum rustc version if the library user experience is improved.
## Why
I've (@TeXitoi) used [docopt](https://crates.io/crates/docopt) for a long time (pre rust 1.0). I really like the fact that you have a structure with the parsed argument: no need to convert `String` to `f64`, no useless `unwrap`. But on the other hand, I don't like to write by hand the usage string. That's like going back to the golden age of WYSIWYG editors. Field naming is also a bit artificial.
Today, the new standard to read command line arguments in Rust is [clap](https://crates.io/crates/clap). This library is so feature full! But I think there is one downside: even if you can validate argument and expressing that an argument is required, you still need to transform something looking like a hashmap of string vectors to something useful for your application.
Now, there is stable custom derive. Thus I can add to clap the automatic conversion that I miss. Here is the result.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

23
clap_derive/appveyor.yml Normal file
View file

@ -0,0 +1,23 @@
environment:
matrix:
- TARGET: x86_64-pc-windows-gnu
RUST_VERSION: stable
- TARGET: x86_64-pc-windows-gnu
RUST_VERSION: nightly
install:
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
- rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION%
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# Building is done in the test phase, so we disable Appveyor's build phase.
build: false
cache:
- C:\Users\appveyor\.cargo\registry
- target
test_script:
- cargo test

View file

@ -0,0 +1,78 @@
# Collection of examples "how to use `clap_derive`"
### [Help on the bottom](after_help.rs)
How to append a postscript to the help message generated.
### [At least N](at_least_two.rs)
How to require presence of at least N values, like `val1 val2 ... valN ... valM`.
### [Basic](basic.rs)
A basic example how to use `clap_derive`.
### [Deny missing docs](deny_missing_docs.rs)
**This is not an example but a test**, it should be moved to `tests` folder
as soon as [this](https://github.com/rust-lang/rust/issues/24584) is fixed (if ever).
### [Doc comments](doc_comments.rs)
How to use doc comments in place of `help/long_help`.
### [Enums as arguments](enum_in_args.rs)
How to use `arg_enum!` with `clap_derive`.
### [Arguments of subcommands in separate `struct`](enum_tuple.rs)
How to extract subcommands' args into external structs.
### [Environment variables](env.rs)
How to use environment variable fallback an how it interacts with `default_value`.
### [Advanced](example.rs)
Somewhat complex example of usage of `clap_derive`.
### [Flatten](flatten.rs)
How to use `#[clap(flatten)]`
### [Git](git.rs)
Pseudo-`git` example, shows how to use subcommands and how to document them.
### [Groups](group.rs)
Using `clap::Arg::group` with `clap`.
### [`key=value` pairs](keyvalue.rs)
How to parse `key=value` pairs.
### [`--no-*` flags](negative_flag.rs)
How to add `no-thing` flag which is `true` by default and `false` if passed.
### [No version](no_version.rs)
How to completely remove version.
### [Rename all](rename_all.rs)
How `#[clap(rename_all)]` works.
### [Skip](skip.rs)
How to use `#[clap(skip)]`.
### [Aliases](subcommand_aliases.rs)
How to use aliases
### [`true` or `false`](true_or_false.rs)
How to express "`"true"` or `"false"` argument.

View file

@ -0,0 +1,19 @@
//! How to append a postscript to the help message generated.
use clap::Clap;
/// I am a program and I do things.
///
/// Sometimes they even work.
#[derive(Clap, Debug)]
#[clap(after_help = "Beware `-d`, dragons be here")]
struct Opt {
/// Release the dragon.
#[clap(short)]
dragon: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,28 @@
// #[macro_use]
// extern crate clap;
// use clap::{App, Arg};
fn main() {}
// #[derive(ArgEnum, Debug)]
// enum ArgChoice {
// Foo,
// Bar,
// Baz,
// }
// fn main() {
// let matches = App::new(env!("CARGO_PKG_NAME"))
// .arg(
// Arg::with_name("arg")
// .required(true)
// .takes_value(true)
// .possible_values(&ArgChoice::variants()),
// )
// .get_matches();
// let t = value_t!(matches.value_of("arg"), ArgChoice).unwrap_or_else(|e| e.exit());
// println!("{:?}", t);
// }

View file

@ -0,0 +1,28 @@
fn main() {}
// #[macro_use]
// extern crate clap;
// use clap::{App, Arg};
// #[derive(ArgEnum, Debug)]
// #[case_sensitive]
// enum ArgChoice {
// Foo,
// Bar,
// Baz,
// }
// fn main() {
// let matches = App::new(env!("CARGO_PKG_NAME"))
// .arg(
// Arg::with_name("arg")
// .required(true)
// .takes_value(true)
// .possible_values(&ArgChoice::variants()),
// )
// .get_matches();
// let t = value_t!(matches.value_of("arg"), ArgChoice).unwrap_or_else(|e| e.exit());
// println!("{:?}", t);
// }

View file

@ -0,0 +1,15 @@
//! How to require presence of at least N values,
//! like `val1 val2 ... valN ... valM`.
use clap::Clap;
#[derive(Clap, Debug)]
struct Opt {
#[clap(required = true, min_values = 2)]
foos: Vec<String>,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,48 @@
//! A somewhat comprehensive example of a typical `StructOpt` usage.use
use clap::Clap;
use std::path::PathBuf;
/// A basic example
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
// A flag, true if used in the command line. Note doc comment will
// be used for the help message of the flag. The name of the
// argument will be, by default, based on the name of the field.
/// Activate debug mode
#[clap(short, long)]
debug: bool,
// The number of occurrences of the `v/verbose` flag
/// Verbose mode (-v, -vv, -vvv, etc.)
#[clap(short, long, parse(from_occurrences))]
verbose: u8,
/// Set speed
#[clap(short, long, default_value = "42")]
speed: f64,
/// Output file
#[clap(short, long, parse(from_os_str))]
output: PathBuf,
// the long option will be translated by default to kebab case,
// i.e. `--nb-cars`.
/// Number of cars
#[clap(short = "c", long)]
nb_cars: Option<i32>,
/// admin_level to consider
#[clap(short, long)]
level: Vec<String>,
/// Files to process
#[clap(name = "FILE", parse(from_os_str))]
files: Vec<PathBuf>,
}
fn main() {
let opt = Opt::parse();
println!("{:#?}", opt);
}

View file

@ -0,0 +1,54 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
//! A test to check that clap_derive compiles with deny(missing_docs)
#![deny(missing_docs)]
use clap::Clap;
/// The options
#[derive(Clap, Debug, PartialEq)]
pub struct Opt {
#[clap(short)]
verbose: bool,
#[clap(subcommand)]
cmd: Option<Cmd>,
}
/// Some subcommands
#[derive(Clap, Debug, PartialEq)]
pub enum Cmd {
/// command A
A,
/// command B
B {
/// Alice?
#[clap(short)]
alice: bool,
},
/// command C
C(COpt),
}
/// The options for C
#[derive(Clap, Debug, PartialEq)]
pub struct COpt {
#[clap(short)]
bob: bool,
}
fn main() {
println!("{:?}", Opt::parse());
}

View file

@ -0,0 +1,74 @@
//! How to use doc comments in place of `help/long_help`.
use clap::Clap;
/// A basic example for the usage of doc comments as replacement
/// of the arguments `help`, `long_help`, `about` and `long_about`.
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
/// Just use doc comments to replace `help`, `long_help`,
/// `about` or `long_about` input.
#[clap(short, long)]
first_flag: bool,
/// Split between `help` and `long_help`.
///
/// In the previous case clap is going to present
/// the whole comment both as text for the `help` and the
/// `long_help` argument.
///
/// But if the doc comment is formatted like this example
/// -- with an empty second line splitting the heading and
/// the rest of the comment -- only the first line is used
/// as `help` argument. The `long_help` argument will still
/// contain the whole comment.
///
/// ## Attention
///
/// Any formatting next to empty lines that could be used
/// inside a doc comment is currently not preserved. If
/// lists or other well formatted content is required it is
/// necessary to use the related clap argument with a
/// raw string as shown on the `third_flag` description.
#[clap(short, long)]
second_flag: bool,
#[clap(
short,
long,
long_help = r"This is a raw string.
It can be used to pass well formatted content (e.g. lists or source
code) in the description:
- first example list entry
- second example list entry
"
)]
third_flag: bool,
#[clap(subcommand)]
sub_command: SubCommand,
}
#[derive(Clap, Debug)]
#[clap()]
enum SubCommand {
/// The same rules described previously for flags. Are
/// also true for in regards of sub-commands.
First,
/// Applicable for both `about` an `help`.
///
/// The formatting rules described in the comment of the
/// `second_flag` also apply to the description of
/// sub-commands which is normally given through the `about`
/// and `long_about` arguments.
Second,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,25 @@
//! How to use `arg_enum!` with `StructOpt`.
// TODO: make it work
fn main() {}
// use clap::Clap;
// arg_enum! {
// #[derive(Debug)]
// enum Baz {
// Foo,
// Bar,
// FooBar
// }
// }
// #[derive(Clap, Debug)]
// struct Opt {
// /// Important argument.
// #[clap(possible_values = &Baz::variants(), case_insensitive = true)]
// i: Baz,
// }
// fn main() {
// let opt = Opt::parse();
// println!("{:?}", opt);
// }

View file

@ -0,0 +1,26 @@
//! How to extract subcommands' args into external structs.
use clap::Clap;
#[derive(Debug, Clap)]
pub struct Foo {
pub bar: Option<String>,
}
#[derive(Debug, Clap)]
pub enum Command {
#[clap(name = "foo")]
Foo(Foo),
}
#[derive(Debug, Clap)]
#[clap(name = "classify")]
pub struct ApplicationArguments {
#[clap(subcommand)]
pub command: Command,
}
fn main() {
let opt = ApplicationArguments::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,26 @@
//! How to use environment variable fallback an how it
//! interacts with `default_value`.
use clap::Clap;
/// Example for allowing to specify options via environment variables.
#[derive(Clap, Debug)]
#[clap(name = "env")]
struct Opt {
// Use `env` to enable specifying the option with an environment
// variable. Command line arguments take precedence over env.
/// URL for the API server
#[clap(long, env = "API_URL")]
api_url: String,
// The default value is used if neither argument nor environment
// variable is specified.
/// Number of retries
#[clap(long, env = "RETRIES", default_value = "5")]
retries: u32,
}
fn main() {
let opt = Opt::parse();
println!("{:#?}", opt);
}

View file

@ -0,0 +1,54 @@
//! Somewhat complex example of usage of structopt.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "example")]
/// An example of clap_derive usage.
struct Opt {
// A flag, true if used in the command line.
#[clap(short, long)]
/// Activate debug mode
debug: bool,
// An argument of type float, with a default value.
#[clap(short, long, default_value = "42")]
/// Set speed
speed: f64,
// Needed parameter, the first on the command line.
/// Input file
input: String,
// An optional parameter, will be `None` if not present on the
// command line.
/// Output file, stdout if not present
output: Option<String>,
// An optional parameter with optional value, will be `None` if
// not present on the command line, will be `Some(None)` if no
// argument is provided (i.e. `--log`) and will be
// `Some(Some(String))` if argument is provided (e.g. `--log
// log.txt`).
#[clap(long)]
#[allow(clippy::option_option)]
/// Log file, stdout if no file, no logging if not present
log: Option<Option<String>>,
// An optional list of values, will be `None` if not present on
// the command line, will be `Some(vec![])` if no argument is
// provided (i.e. `--optv`) and will be `Some(Some(String))` if
// argument list is provided (e.g. `--optv a b c`).
#[clap(long)]
optv: Option<Vec<String>>,
// Skipped option: it won't be parsed and will be filled with the
// default value for its type (in this case it'll be an empty string).
#[clap(skip)]
skipped: String,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,29 @@
//! How to use flattening.
use clap::Clap;
#[derive(Clap, Debug)]
struct Cmdline {
/// switch verbosity on
#[clap(short)]
verbose: bool,
#[clap(flatten)]
daemon_opts: DaemonOpts,
}
#[derive(Clap, Debug)]
struct DaemonOpts {
/// daemon user
#[clap(short)]
user: String,
/// daemon group
#[clap(short)]
group: String,
}
fn main() {
let opt = Cmdline::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,35 @@
//! `git.rs` serves as a demonstration of how to use subcommands,
//! as well as a demonstration of adding documentation to subcommands.
//! Documentation can be added either through doc comments or
//! `help`/`about` attributes.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "git")]
/// the stupid content tracker
enum Opt {
/// fetch branches from remote repository
Fetch {
#[clap(long)]
dry_run: bool,
#[clap(long)]
all: bool,
#[clap(default_value = "origin")]
repository: String,
},
#[clap(override_help = "add files to the staging area")]
Add {
#[clap(short)]
interactive: bool,
#[clap(short)]
all: bool,
files: Vec<String>,
},
}
fn main() {
let matches = Opt::parse();
println!("{:?}", matches);
}

View file

@ -0,0 +1,31 @@
//! How to use `clap::Arg::group`
use clap::{ArgGroup, Clap};
#[derive(Clap, Debug)]
#[clap(group = ArgGroup::with_name("verb").required(true))]
struct Opt {
/// Set a custom HTTP verb
#[clap(long, group = "verb")]
method: Option<String>,
/// HTTP GET
#[clap(long, group = "verb")]
get: bool,
/// HTTP HEAD
#[clap(long, group = "verb")]
head: bool,
/// HTTP POST
#[clap(long, group = "verb")]
post: bool,
/// HTTP PUT
#[clap(long, group = "verb")]
put: bool,
/// HTTP DELETE
#[clap(long, group = "verb")]
delete: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,36 @@
//! How to parse "key=value" pairs with structopt.
use clap::Clap;
use std::error::Error;
/// Parse a single key-value pair
fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error>>
where
T: std::str::FromStr,
T::Err: Error + 'static,
U: std::str::FromStr,
U::Err: Error + 'static,
{
let pos = s
.find('=')
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}
#[derive(Clap, Debug)]
struct Opt {
// number_of_values = 1 forces the user to repeat the -D option for each key-value pair:
// my_program -D a=1 -D b=2
// Without number_of_values = 1 you can do:
// my_program -D a=1 b=2
// but this makes adding an argument after the values impossible:
// my_program -D a=1 -D b=2 my_input_file
// becomes invalid.
#[clap(short = "D", parse(try_from_str = parse_key_val), number_of_values = 1)]
defines: Vec<(String, i32)>,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,15 @@
//! How to add `no-thing` flag which is `true` by default and
//! `false` if passed.
use clap::Clap;
#[derive(Debug, Clap)]
struct Opt {
#[clap(long = "no-verbose", parse(from_flag = std::ops::Not::not))]
verbose: bool,
}
fn main() {
let cmd = Opt::parse();
println!("{:#?}", cmd);
}

View file

@ -0,0 +1,16 @@
//! How to completely remove version.
use clap::{AppSettings, Clap};
#[derive(Clap, Debug)]
#[clap(
name = "no_version",
no_version,
global_setting = AppSettings::DisableVersion
)]
struct Opt {}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,74 @@
//! Example on how the `rename_all` parameter works.
//!
//! `rename_all` can be used to override the casing style used during argument
//! generation. By default the `kebab-case` style will be used but there are a wide
//! variety of other styles available.
//!
//! ## Supported styles overview:
//!
//! - **Camel Case**: Indicate word boundaries with uppercase letter, excluding
//! the first word.
//! - **Kebab Case**: Keep all letters lowercase and indicate word boundaries
//! with hyphens.
//! - **Pascal Case**: Indicate word boundaries with uppercase letter,
//! including the first word.
//! - **Screaming Snake Case**: Keep all letters uppercase and indicate word
//! boundaries with underscores.
//! - **Snake Case**: Keep all letters lowercase and indicate word boundaries
//! with underscores.
//! - **Verbatim**: Use the original attribute name defined in the code.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "rename_all", rename_all = "screaming_snake_case")]
enum Opt {
// This subcommand will be named `FIRST_COMMAND`. As the command doesn't
// override the initial casing style, ...
/// A screaming loud first command. Only use if necessary.
FirstCommand {
// this flag will be available as `--FOO` and `-F`.
/// This flag will even scream louder.
#[clap(long, short)]
foo: bool,
},
// As we override the casing style for this variant the related subcommand
// will be named `SecondCommand`.
/// Not nearly as loud as the first command.
#[clap(rename_all = "pascal_case")]
SecondCommand {
// We can also override it again on a single field.
/// Nice quiet flag. No one is annoyed.
#[clap(rename_all = "snake_case", long)]
bar_option: bool,
// Renaming will not be propagated into subcommand flagged enums. If
// a non default casing style is required it must be defined on the
// enum itself.
#[clap(subcommand)]
cmds: Subcommands,
// or flattened structs.
#[clap(flatten)]
options: BonusOptions,
},
}
#[derive(Clap, Debug)]
enum Subcommands {
// This one will be available as `first-subcommand`.
FirstSubcommand,
}
#[derive(Clap, Debug)]
struct BonusOptions {
// And this one will be available as `baz-option`.
#[clap(long)]
baz_option: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,47 @@
//! How to use `#[structopt(skip)]`
use clap::Clap;
#[derive(Clap, Debug, PartialEq)]
pub struct Opt {
#[clap(long, short)]
number: u32,
#[clap(skip)]
k: Kind,
#[clap(skip)]
v: Vec<u32>,
#[clap(skip = Kind::A)]
k2: Kind,
#[clap(skip = vec![1, 2, 3])]
v2: Vec<u32>,
#[clap(skip = "cake")] // &str implements Into<String>
s: String,
}
#[derive(Debug, PartialEq)]
enum Kind {
A,
B,
}
impl Default for Kind {
fn default() -> Self {
return Kind::B;
}
}
fn main() {
assert_eq!(
Opt::parse_from(&["test", "-n", "10"]),
Opt {
number: 10,
k: Kind::B,
v: vec![],
k2: Kind::A,
v2: vec![1, 2, 3],
s: String::from("cake")
}
);
}

View file

@ -0,0 +1,20 @@
//! How to assign some aliases to subcommands
use clap::{AppSettings, Clap};
#[derive(Clap, Debug)]
// https://docs.rs/clap/2/clap/enum.AppSettings.html#variant.InferSubcommands
#[clap(setting = AppSettings::InferSubcommands)]
enum Opt {
// https://docs.rs/clap/2/clap/struct.App.html#method.alias
#[clap(alias = "foobar")]
Foo,
// https://docs.rs/clap/2/clap/struct.App.html#method.aliases
#[clap(aliases = &["baz", "fizz"])]
Bar,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,41 @@
//! How to parse `--foo=true --bar=false` and turn them into bool.
use clap::Clap;
fn true_or_false(s: &str) -> Result<bool, &'static str> {
match s {
"true" => Ok(true),
"false" => Ok(false),
_ => Err("expected `true` or `false`"),
}
}
#[derive(Clap, Debug, PartialEq)]
struct Opt {
// Default parser for `try_from_str` is FromStr::from_str.
// `impl FromStr for bool` parses `true` or `false` so this
// works as expected.
#[clap(long, parse(try_from_str))]
foo: bool,
// Of course, this could be done with an explicit parser function.
#[clap(long, parse(try_from_str = true_or_false))]
bar: bool,
// `bool` can be positional only with explicit `parse(...)` annotation
#[clap(long, parse(try_from_str))]
boom: bool,
}
fn main() {
assert_eq!(
Opt::parse_from(&["test", "--foo=true", "--bar=false", "true"]),
Opt {
foo: true,
bar: false,
boom: true
}
);
// no beauty, only truth and falseness
assert!(Opt::try_parse_from(&["test", "--foo=beauty"]).is_err());
}

61
clap_derive/justfile Normal file
View file

@ -0,0 +1,61 @@
@update-contributors:
echo 'Removing old CONTRIBUTORS.md'
mv CONTRIBUTORS.md CONTRIBUTORS.md.bak
echo 'Downloading a list of new contributors'
echo "the following is a list of contributors:" > CONTRIBUTORS.md
echo "" >> CONTRIBUTORS.md
echo "" >> CONTRIBUTORS.md
githubcontrib --owner kbknapp --repo clap_derive --sha master --cols 6 --format md --showlogin true --sortBy contributions --sortOrder desc >> CONTRIBUTORS.md
echo "" >> CONTRIBUTORS.md
echo "" >> CONTRIBUTORS.md
echo "This list was generated by [mgechev/github-contributors-list](https://github.com/mgechev/github-contributors-list)" >> CONTRIBUTORS.md
rm CONTRIBUTORS.md.bak
run-test TESTG TEST="":
cargo test --test {{TESTG}} -- {{TEST}}
debug TESTG TEST="":
cargo test --test {{TESTG}} --features debug -- {{TEST}}
run-tests:
cargo test
@bench: nightly
cargo bench && just remove-nightly
nightly:
rustup override add nightly
remove-nightly:
rustup override remove
@lint: nightly
cargo build --features lints && just remove-nightly
clean:
cargo clean
find . -type f -name "*.orig" -exec rm {} \;
find . -type f -name "*.bk" -exec rm {} \;
find . -type f -name ".*~" -exec rm {} \;
top-errors NUM="95":
@cargo check 2>&1 | head -n {{NUM}}
count-errors:
@cargo check 2>&1 | grep -e '^error' | wc -l
find-errors:
@cargo check 2>&1 | grep --only-matching -e '-->[^:]*' | sort | uniq -c | sort -nr
count-warnings:
@cargo check 2>&1 | grep -e '^warning' | wc -l
find-warnings:
@cargo check 2>&1 | grep -A1 -e 'warning' | grep --only-matching -e '-->[^:]*' | sort | uniq -c | sort -nr
@update-todo:
./etc/update-todo.sh
@count-failures:
./etc/count-tests.sh

2
clap_derive/rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
format_strings = false
fn_single_line = true

View file

@ -0,0 +1,94 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use proc_macro2;
// use quote;
use syn;
// use syn::punctuated;
// use syn::token;
pub fn derive_arg_enum(_ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
unimplemented!()
// let from_str_block = impl_from_str(ast)?;
// let variants_block = impl_variants(ast)?;
// quote! {
// #from_str_block
// #variants_block
// }
}
/*
fn impl_from_str(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
let ident = &ast.ident;
let is_case_sensitive = ast.attrs.iter().any(|v| v.name() == "case_sensitive");
let variants = variants(ast)?;
let strings = variants
.iter()
.map(|ref variant| String::from(variant.ident.as_ref()))
.collect::<Vec<_>>();
// All of these need to be iterators.
let ident_slice = [ident.clone()];
let idents = ident_slice.iter().cycle();
let for_error_message = strings.clone();
let condition_function_slice = [match is_case_sensitive {
true => quote! { str::eq },
false => quote! { ::std::ascii::AsciiExt::eq_ignore_ascii_case },
}];
let condition_function = condition_function_slice.iter().cycle();
Ok(quote! {
impl ::std::str::FromStr for #ident {
type Err = String;
fn from_str(input: &str) -> ::std::result::Result<Self, Self::Err> {
match input {
#(val if #condition_function(val, #strings) => Ok(#idents::#variants),)*
_ => Err({
let v = #for_error_message;
format!("valid values: {}",
v.join(" ,"))
}),
}
}
}
})
}
fn impl_variants(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
let ident = &ast.ident;
let variants = variants(ast)?
.iter()
.map(|ref variant| String::from(variant.ident.as_ref()))
.collect::<Vec<_>>();
let length = variants.len();
Ok(quote! {
impl #ident {
fn variants() -> [&'static str; #length] {
#variants
}
}
})
}
fn variants(ast: &syn::DeriveInput) -> &punctuated::Punctuated<syn::Variant, token::Comma> {
use syn::Data::*;
match ast.data {
Enum(ref data) => data.variants,
_ => panic!("Only enums are supported for deriving the ArgEnum trait"),
}
}
*/

View file

@ -0,0 +1,663 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use super::{parse::*, spanned::Sp, ty::Ty};
use std::env;
use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase};
use proc_macro2::{self, Span, TokenStream};
use proc_macro_error::abort;
use quote::{quote, quote_spanned, ToTokens};
use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Ident, LitStr, MetaNameValue};
/// Default casing style for generated arguments.
pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
#[derive(Clone)]
pub enum Kind {
Arg(Sp<Ty>),
Subcommand(Sp<Ty>),
FlattenStruct,
Skip(Option<syn::Expr>),
}
#[derive(Clone)]
pub struct Method {
name: syn::Ident,
args: proc_macro2::TokenStream,
}
#[derive(Clone)]
pub struct Parser {
pub kind: Sp<ParserKind>,
pub func: proc_macro2::TokenStream,
}
#[derive(Debug, PartialEq, Clone)]
pub enum ParserKind {
FromStr,
TryFromStr,
FromOsStr,
TryFromOsStr,
FromOccurrences,
FromFlag,
}
/// Defines the casing for the attributes long representation.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CasingStyle {
/// Indicate word boundaries with uppercase letter, excluding the first word.
Camel,
/// Keep all letters lowercase and indicate word boundaries with hyphens.
Kebab,
/// Indicate word boundaries with uppercase letter, including the first word.
Pascal,
/// Keep all letters uppercase and indicate word boundaries with underscores.
ScreamingSnake,
/// Keep all letters lowercase and indicate word boundaries with underscores.
Snake,
/// Use the original attribute name defined in the code.
Verbatim,
}
#[derive(Clone)]
pub enum Name {
Derived(syn::Ident),
Assigned(syn::LitStr),
}
#[derive(Clone)]
pub struct Attrs {
name: Name,
casing: Sp<CasingStyle>,
methods: Vec<Method>,
parser: Sp<Parser>,
author: Option<Method>,
about: Option<Method>,
version: Option<Method>,
no_version: Option<syn::Ident>,
has_custom_parser: bool,
kind: Sp<Kind>,
}
/// Output for the gen_xxx() methods were we need more than a simple stream of tokens.
///
/// The output of a generation method is not only the stream of new tokens but also the attribute
/// information of the current element. These attribute information may contain valuable information
/// for any kind of child arguments.
pub struct GenOutput {
pub tokens: proc_macro2::TokenStream,
pub attrs: Attrs,
}
impl Method {
fn new(name: syn::Ident, args: proc_macro2::TokenStream) -> Self {
Method { name, args }
}
fn from_lit_or_env(ident: syn::Ident, lit: Option<syn::LitStr>, env_var: &str) -> Option<Self> {
let mut lit = match lit {
Some(lit) => lit,
None => match env::var(env_var) {
Ok(val) => syn::LitStr::new(&val, ident.span()),
Err(_) => {
abort!(ident.span(),
"cannot derive `{}` from Cargo.toml", ident;
note = "`{}` environment variable is not set", env_var;
help = "use `{} = \"...\"` to set {} manually", ident, ident;
);
}
},
};
if ident == "author" {
let edited = process_author_str(&lit.value());
lit = syn::LitStr::new(&edited, lit.span());
}
Some(Method::new(ident, quote!(#lit)))
}
}
impl ToTokens for Method {
fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
let Method { ref name, ref args } = self;
let tokens = if name == "short" {
quote!( .#name(#args.chars().nth(0).unwrap()) )
} else {
quote!( .#name(#args) )
};
tokens.to_tokens(ts);
}
}
impl Parser {
fn default_spanned(span: Span) -> Sp<Self> {
let kind = Sp::new(ParserKind::TryFromStr, span);
let func = quote_spanned!(span=> ::std::str::FromStr::from_str);
Sp::new(Parser { kind, func }, span)
}
fn from_spec(parse_ident: syn::Ident, spec: ParserSpec) -> Sp<Self> {
use self::ParserKind::*;
let kind = match &*spec.kind.to_string() {
"from_str" => FromStr,
"try_from_str" => TryFromStr,
"from_os_str" => FromOsStr,
"try_from_os_str" => TryFromOsStr,
"from_occurrences" => FromOccurrences,
"from_flag" => FromFlag,
s => abort!(spec.kind.span(), "unsupported parser `{}`", s),
};
let func = match spec.parse_func {
None => match kind {
FromStr | FromOsStr => {
quote_spanned!(spec.kind.span()=> ::std::convert::From::from)
}
TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str),
TryFromOsStr => abort!(
spec.kind.span(),
"you must set parser for `try_from_os_str` explicitly"
),
FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }),
FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from),
},
Some(func) => match func {
syn::Expr::Path(_) => quote!(#func),
_ => abort!(func.span(), "`parse` argument must be a function path"),
},
};
let kind = Sp::new(kind, spec.kind.span());
let parser = Parser { kind, func };
Sp::new(parser, parse_ident.span())
}
}
impl CasingStyle {
fn from_lit(name: syn::LitStr) -> Sp<Self> {
use self::CasingStyle::*;
let normalized = name.value().to_camel_case().to_lowercase();
let cs = |kind| Sp::new(kind, name.span());
match normalized.as_ref() {
"camel" | "camelcase" => cs(Camel),
"kebab" | "kebabcase" => cs(Kebab),
"pascal" | "pascalcase" => cs(Pascal),
"screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
"snake" | "snakecase" => cs(Snake),
"verbatim" | "verbatimcase" => cs(Verbatim),
s => abort!(name.span(), "unsupported casing: `{}`", s),
}
}
}
impl Name {
pub fn translate(self, style: CasingStyle) -> LitStr {
use self::CasingStyle::*;
match self {
Name::Assigned(lit) => lit,
Name::Derived(ident) => {
let s = ident.unraw().to_string();
let s = match style {
Pascal => s.to_camel_case(),
Kebab => s.to_kebab_case(),
Camel => s.to_mixed_case(),
ScreamingSnake => s.to_shouty_snake_case(),
Snake => s.to_snake_case(),
Verbatim => s,
};
syn::LitStr::new(&s, ident.span())
}
}
}
}
impl Attrs {
fn new(default_span: Span, name: Name, casing: Sp<CasingStyle>) -> Self {
Self {
name,
casing,
methods: vec![],
parser: Parser::default_spanned(default_span),
about: None,
author: None,
version: None,
no_version: None,
has_custom_parser: false,
kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
}
}
/// push `.method("str literal")`
fn push_str_method(&mut self, name: Sp<String>, arg: Sp<String>) {
match (&**name, &**arg) {
("name", _) => {
self.name = Name::Assigned(arg.as_lit());
}
_ => self
.methods
.push(Method::new(name.as_ident(), quote!(#arg))),
}
}
fn push_attrs(&mut self, attrs: &[syn::Attribute]) {
use ClapAttr::*;
for attr in parse_clap_attributes(attrs) {
match attr {
Short(ident) | Long(ident) => {
self.push_str_method(
ident.into(),
self.name.clone().translate(*self.casing).into(),
);
}
Subcommand(ident) => {
let ty = Sp::call_site(Ty::Other);
let kind = Sp::new(Kind::Subcommand(ty), ident.span());
self.set_kind(kind);
}
Flatten(ident) => {
let kind = Sp::new(Kind::FlattenStruct, ident.span());
self.set_kind(kind);
}
Skip(ident, expr) => {
let kind = Sp::new(Kind::Skip(expr), ident.span());
self.set_kind(kind);
}
NoVersion(ident) => self.no_version = Some(ident),
About(ident, about) => {
self.about = Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION");
}
Author(ident, author) => {
self.author = Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS");
}
Version(ident, version) => {
self.version = Some(Method::new(ident, quote!(#version)))
}
NameLitStr(name, lit) => {
self.push_str_method(name.into(), lit.into());
}
NameExpr(name, expr) => self.methods.push(Method::new(name, quote!(#expr))),
MethodCall(name, args) => self.methods.push(Method::new(name, quote!(#(#args),*))),
RenameAll(_, casing_lit) => {
self.casing = CasingStyle::from_lit(casing_lit);
}
Parse(ident, spec) => {
self.has_custom_parser = true;
self.parser = Parser::from_spec(ident, spec);
}
}
}
}
fn push_doc_comment(&mut self, attrs: &[syn::Attribute], name: &str) {
let doc_comments = attrs
.iter()
.filter_map(|attr| {
if attr.path.is_ident("doc") {
attr.parse_meta().ok()
} else {
None
}
})
.filter_map(|attr| {
use syn::Lit::*;
use syn::Meta::*;
if let NameValue(syn::MetaNameValue {
path, lit: Str(s), ..
}) = attr
{
if !path.is_ident("doc") {
return None;
}
let value = s.value();
let text = value
.trim_start_matches("//!")
.trim_start_matches("///")
.trim_start_matches("/*!")
.trim_start_matches("/**")
.trim_end_matches("*/")
.trim();
if text.is_empty() {
Some("\n\n".to_string())
} else {
Some(text.to_string())
}
} else {
None
}
})
.collect::<Vec<_>>();
if doc_comments.is_empty() {
return;
}
let merged_lines = doc_comments
.join(" ")
.split('\n')
.map(str::trim)
.map(str::to_string)
.collect::<Vec<_>>()
.join("\n");
let expected_doc_comment_split = if let Some(content) = doc_comments.get(1) {
(doc_comments.len() > 2) && (content == &"\n\n")
} else {
false
};
if expected_doc_comment_split {
let long_name = Sp::call_site(format!("long_{}", name));
self.methods
.push(Method::new(long_name.as_ident(), quote!(#merged_lines)));
// Remove trailing whitespace and period from short help, as rustdoc
// best practice is to use complete sentences, but command-line help
// typically omits the trailing period.
let short_arg = doc_comments
.first()
.map(|s| s.trim())
.map_or("", |s| s.trim_end_matches('.'));
self.methods.push(Method::new(
syn::Ident::new(name, Span::call_site()),
quote!(#short_arg),
));
} else {
self.methods.push(Method::new(
syn::Ident::new(name, Span::call_site()),
quote!(#merged_lines),
));
}
}
pub fn from_struct(
span: Span,
attrs: &[syn::Attribute],
name: Name,
argument_casing: Sp<CasingStyle>,
) -> Self {
let mut res = Self::new(span, name, argument_casing);
res.push_attrs(attrs);
res.push_doc_comment(attrs, "about");
if res.has_custom_parser {
abort!(
res.parser.span(),
"`parse` attribute is only allowed on fields"
);
}
match &*res.kind {
Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
Kind::FlattenStruct => abort!(res.kind.span(), "flatten is only allowed on fields"),
Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"),
Kind::Arg(_) => res,
}
}
pub fn from_field(field: &syn::Field, struct_casing: Sp<CasingStyle>) -> Self {
let name = field.ident.clone().unwrap();
let mut res = Self::new(field.span(), Name::Derived(name.clone()), struct_casing);
res.push_doc_comment(&field.attrs, "help");
res.push_attrs(&field.attrs);
match &*res.kind {
Kind::FlattenStruct => {
if res.has_custom_parser {
abort!(
res.parser.span(),
"parse attribute is not allowed for flattened entry"
);
}
if res.has_explicit_methods() || res.has_doc_methods() {
abort!(
res.kind.span(),
"methods and doc comments are not allowed for flattened entry"
);
}
}
Kind::Subcommand(_) => {
if res.has_custom_parser {
abort!(
res.parser.span(),
"parse attribute is not allowed for subcommand"
);
}
if res.has_explicit_methods() {
abort!(
res.kind.span(),
"methods in attributes are not allowed for subcommand"
);
}
let ty = Ty::from_syn_ty(&field.ty);
match *ty {
Ty::OptionOption => {
abort!(
ty.span(),
"Option<Option<T>> type is not allowed for subcommand"
);
}
Ty::OptionVec => {
abort!(
ty.span(),
"Option<Vec<T>> type is not allowed for subcommand"
);
}
_ => (),
}
res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
}
Kind::Skip(_) => {
if res.has_explicit_methods() {
abort!(
res.kind.span(),
"methods are not allowed for skipped fields"
);
}
}
Kind::Arg(orig_ty) => {
let mut ty = Ty::from_syn_ty(&field.ty);
if res.has_custom_parser {
match *ty {
Ty::Option | Ty::Vec | Ty::OptionVec => (),
_ => ty = Sp::new(Ty::Other, ty.span()),
}
}
match *ty {
Ty::Bool => {
if res.is_positional() && !res.has_custom_parser {
abort!(ty.span(),
"`bool` cannot be used as positional parameter with default parser";
help = "if you want to create a flag add `long` or `short`";
help = "If you really want a boolean parameter \
add an explicit parser, for example `parse(try_from_str)`";
note = "see also https://github.com/clap-rs/clap_derive/tree/master/examples/true_or_false.rs";
)
}
if let Some(m) = res.find_method("default_value") {
abort!(m.name.span(), "default_value is meaningless for bool")
}
if let Some(m) = res.find_method("required") {
abort!(m.name.span(), "required is meaningless for bool")
}
}
Ty::Option => {
if let Some(m) = res.find_method("default_value") {
abort!(m.name.span(), "default_value is meaningless for Option")
}
if let Some(m) = res.find_method("required") {
abort!(m.name.span(), "required is meaningless for Option")
}
}
Ty::OptionOption => {
if res.is_positional() {
abort!(
ty.span(),
"Option<Option<T>> type is meaningless for positional argument"
)
}
}
Ty::OptionVec => {
if res.is_positional() {
abort!(
ty.span(),
"Option<Vec<T>> type is meaningless for positional argument"
)
}
}
_ => (),
}
res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());
}
}
res
}
fn set_kind(&mut self, kind: Sp<Kind>) {
if let Kind::Arg(_) = *self.kind {
self.kind = kind;
} else {
abort!(
kind.span(),
"subcommand, flatten and skip cannot be used together"
);
}
}
pub fn has_method(&self, name: &str) -> bool {
self.find_method(name).is_some()
}
pub fn find_method(&self, name: &str) -> Option<&Method> {
self.methods.iter().find(|m| m.name == name)
}
/// generate methods from attributes on top of struct or enum
pub fn top_level_methods(&self) -> proc_macro2::TokenStream {
let version = match (&self.no_version, &self.version) {
(Some(no_version), Some(_)) => abort!(
no_version.span(),
"`no_version` and `version = \"version\"` can't be used together"
),
(None, Some(m)) => m.to_token_stream(),
(None, None) => std::env::var("CARGO_PKG_VERSION")
.map(|version| quote!( .version(#version) ))
.unwrap_or_default(),
(Some(_), None) => quote!(),
};
let author = &self.author;
let about = &self.about;
let methods = &self.methods;
quote!( #author #version #(#methods)* #about )
}
/// generate methods on top of a field
pub fn field_methods(&self) -> proc_macro2::TokenStream {
let methods = &self.methods;
quote!( #(#methods)* )
}
pub fn cased_name(&self) -> LitStr {
self.name.clone().translate(*self.casing)
}
pub fn parser(&self) -> &Sp<Parser> {
&self.parser
}
pub fn kind(&self) -> Sp<Kind> {
self.kind.clone()
}
pub fn casing(&self) -> Sp<CasingStyle> {
self.casing.clone()
}
pub fn is_positional(&self) -> bool {
self.methods
.iter()
.all(|m| m.name != "long" && m.name != "short")
}
pub fn has_explicit_methods(&self) -> bool {
self.methods
.iter()
.any(|m| m.name != "help" && m.name != "long_help")
}
pub fn has_doc_methods(&self) -> bool {
self.methods
.iter()
.any(|m| m.name == "help" || m.name == "long_help")
}
}
/// replace all `:` with `, ` when not inside the `<>`
///
/// `"author1:author2:author3" => "author1, author2, author3"`
/// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2"
fn process_author_str(author: &str) -> String {
let mut res = String::with_capacity(author.len());
let mut inside_angle_braces = 0usize;
for ch in author.chars() {
if inside_angle_braces > 0 && ch == '>' {
inside_angle_braces -= 1;
res.push(ch);
} else if ch == '<' {
inside_angle_braces += 1;
res.push(ch);
} else if inside_angle_braces == 0 && ch == ':' {
res.push_str(", ");
} else {
res.push(ch);
}
}
res
}

View file

@ -0,0 +1,427 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use proc_macro2;
use proc_macro_error::{abort, abort_call_site, set_dummy};
use syn::{self, punctuated, spanned::Spanned, token};
use super::{from_argmatches, into_app, sub_type, Attrs, Kind, Name, ParserKind, Ty};
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an app.
fn gen_app_augmentation(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
app_var: &syn::Ident,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let mut subcmds = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(&field, parent_attribute.casing());
let kind = attrs.kind();
if let Kind::Subcommand(ty) = &*kind {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let required = if **ty == Ty::Option {
quote!()
} else {
quote_spanned! { kind.span()=>
let #app_var = #app_var.setting(
::clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
let span = field.span();
let ts = quote! {
let #app_var = <#subcmd_type>::augment_app( #app_var );
#required
};
Some((span, ts))
} else {
None
}
});
let subcmd = subcmds.next().map(|(_, ts)| ts);
if let Some((span, _)) = subcmds.next() {
abort!(
span,
"multiple subcommand sets are not allowed, that's the second"
);
}
let args = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(field, parent_attribute.casing());
let kind = attrs.kind();
match &*kind {
Kind::Subcommand(_) | Kind::Skip(_) => None,
Kind::FlattenStruct => {
let ty = &field.ty;
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty>::augment_app(#app_var);
let #app_var = if <#ty>::is_subcommand() {
#app_var.setting(::clap::AppSettings::SubcommandRequiredElseHelp)
} else {
#app_var
};
})
}
Kind::Arg(ty) => {
let convert_type = match **ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
Ty::OptionOption | Ty::OptionVec => {
sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
}
_ => &field.ty,
};
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let parser = attrs.parser();
let func = &parser.func;
let validator = match *parser.kind {
ParserKind::TryFromStr => quote_spanned! { func.span()=>
.validator(|s| {
#func(s.as_str())
.map(|_: #convert_type| ())
.map_err(|e| e.to_string())
})
},
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
.validator_os(|s| #func(&s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
let modifier = match **ty {
Ty::Bool => quote!(),
Ty::Option => quote_spanned! { ty.span()=>
.takes_value(true)
#validator
},
Ty::OptionOption => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(false)
.min_values(0)
.max_values(1)
#validator
},
Ty::OptionVec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
.min_values(0)
#validator
},
Ty::Vec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
#validator
},
Ty::Other if occurrences => quote_spanned! { ty.span()=>
.multiple_occurrences(true)
},
Ty::Other if flag => quote_spanned! { ty.span()=>
.takes_value(false)
.multiple(false)
},
Ty::Other => {
let required = !attrs.has_method("default_value");
quote_spanned! { ty.span()=>
.takes_value(true)
.required(#required)
#validator
}
}
};
let name = attrs.cased_name();
let methods = attrs.field_methods();
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg(
::clap::Arg::with_name(#name)
#modifier
#methods
);
})
}
}
});
let app_methods = parent_attribute.top_level_methods();
quote! {{
let #app_var = #app_var#app_methods;
#( #args )*
#subcmd
#app_var
}}
}
fn gen_augment_app_fn(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let app_var = syn::Ident::new("app", proc_macro2::Span::call_site());
let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute);
quote! {
pub fn augment_app<'b>(
#app_var: ::clap::App<'b>
) -> ::clap::App<'b> {
#augmentation
}
}
}
fn gen_augment_app_for_enum(
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let subcommands = variants.iter().map(|variant| {
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
);
let app_var = syn::Ident::new("subcommand", proc_macro2::Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => gen_app_augmentation(&fields.named, &app_var, &attrs),
Unit => quote!( #app_var ),
Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote_spanned! { ty.span() =>
{
let #app_var = <#ty>::augment_app(#app_var);
if <#ty>::is_subcommand() {
#app_var.setting(
::clap::AppSettings::SubcommandRequiredElseHelp
)
} else {
#app_var
}
}
}
}
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
let name = attrs.cased_name();
let from_attrs = attrs.top_level_methods();
quote! {
.subcommand({
let #app_var = ::clap::App::new(#name);
let #app_var = #arg_block;
#app_var#from_attrs
})
}
});
let app_methods = parent_attribute.top_level_methods();
quote! {
pub fn augment_app<'b>(
app: ::clap::App<'b>
) -> ::clap::App<'b> {
app #app_methods #( #subcommands )*
}
}
}
fn gen_from_subcommand(
name: &syn::Ident,
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let match_arms = variants.iter().map(|variant| {
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
);
let sub_name = attrs.cased_name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
Named(ref fields) => from_argmatches::gen_constructor(&fields.named, &attrs),
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote!( ( <#ty as ::clap::FromArgMatches>::from_argmatches(matches) ) )
}
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
quote! {
(#sub_name, Some(matches)) =>
Some(#name :: #variant_name #constructor_block)
}
});
quote! {
pub fn from_subcommand<'b>(
sub: (&'b str, Option<&'b ::clap::ArgMatches>)
) -> Option<Self> {
match sub {
#( #match_arms ),*,
_ => None
}
}
}
}
fn clap_impl_for_struct(
name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
let into_app_impl = into_app::gen_into_app_impl_for_struct(name, attrs);
let into_app_impl_tokens = into_app_impl.tokens;
let augment_app_fn = gen_augment_app_fn(fields, &into_app_impl.attrs);
let from_argmatches_impl =
from_argmatches::gen_from_argmatches_impl_for_struct(name, fields, &into_app_impl.attrs);
let parse_fns = gen_parse_fns(name);
quote! {
#[allow(unused_variables)]
impl ::clap::Clap for #name { }
#into_app_impl_tokens
#from_argmatches_impl
#[allow(dead_code, unreachable_code)]
#[doc(hidden)]
impl #name {
#augment_app_fn
#parse_fns
pub fn is_subcommand() -> bool { false }
}
}
}
fn clap_impl_for_enum(
name: &syn::Ident,
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
let into_app_impl = into_app::gen_into_app_impl_for_enum(name, attrs);
let into_app_impl_tokens = into_app_impl.tokens;
let augment_app_fn = gen_augment_app_for_enum(variants, &into_app_impl.attrs);
let from_argmatches_impl = from_argmatches::gen_from_argmatches_impl_for_enum(name);
let from_subcommand = gen_from_subcommand(name, variants, &into_app_impl.attrs);
let parse_fns = gen_parse_fns(name);
quote! {
#[allow(unused_variables)]
impl ::clap::Clap for #name { }
#into_app_impl_tokens
#from_argmatches_impl
#[allow(unused_variables, dead_code, unreachable_code)]
#[doc(hidden)]
impl #name {
#augment_app_fn
#from_subcommand
#parse_fns
pub fn is_subcommand() -> bool { true }
}
}
}
pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
set_dummy(quote! {
impl ::clap::Clap for #struct_name {}
impl ::clap::IntoApp for #struct_name {
fn into_app<'b>() -> ::clap::App<'b> {
unimplemented!()
}
}
impl ::clap::FromArgMatches for #struct_name {
fn from_argmatches(m: &::clap::ArgMatches) -> Self {
unimplemented!()
}
}
impl #struct_name {
fn parse() -> Self {
unimplemented!();
}
}
});
match input.data {
Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => clap_impl_for_struct(struct_name, &fields.named, &input.attrs),
Enum(ref e) => clap_impl_for_enum(struct_name, &e.variants, &input.attrs),
_ => abort_call_site!("clap_derive only supports non-tuple structs and enums"),
}
}
fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
#[allow(unreachable_pub)]
pub fn parse() -> #name {
use ::clap::{FromArgMatches, IntoApp};
#name::from_argmatches(&#name::into_app().get_matches())
}
#[allow(unreachable_pub)]
pub fn try_parse() -> ::std::result::Result<#name, ::clap::Error> {
use ::clap::{FromArgMatches, IntoApp};
Ok(#name::from_argmatches(&#name::into_app().try_get_matches()?))
}
#[allow(unreachable_pub)]
pub fn parse_from<I, T>(itr: I) -> #name
where
I: ::std::iter::IntoIterator<Item = T>,
T: Into<::std::ffi::OsString> + Clone {
use ::clap::{FromArgMatches, IntoApp};
#name::from_argmatches(&#name::into_app().get_matches_from(itr))
}
#[allow(unreachable_pub)]
pub fn try_parse_from<I, T>(itr: I) -> ::std::result::Result<#name, ::clap::Error>
where
I: ::std::iter::IntoIterator<Item = T>,
T: Into<::std::ffi::OsString> + Clone {
use ::clap::{FromArgMatches, IntoApp};
Ok(#name::from_argmatches(&#name::into_app().try_get_matches_from(itr)?))
}
}
}

View file

@ -0,0 +1,237 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use std::env;
use proc_macro2;
use syn;
use syn::punctuated;
use syn::spanned::Spanned as _;
use syn::token;
use super::{spanned::Sp, sub_type, Attrs, Kind, Name, ParserKind, Ty, DEFAULT_CASING};
pub fn derive_from_argmatches(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
let inner_impl = match input.data {
Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => {
let name = env::var("CARGO_PKG_NAME")
.ok()
.unwrap_or_else(String::default);
let attrs = Attrs::from_struct(
proc_macro2::Span::call_site(),
&input.attrs,
Name::Assigned(syn::LitStr::new(&name, proc_macro2::Span::call_site())),
Sp::call_site(DEFAULT_CASING),
);
gen_from_argmatches_impl_for_struct(struct_name, &fields.named, &attrs)
}
// Enum(ref e) => clap_for_enum_impl(struct_name, &e.variants, &input.attrs),
_ => panic!("clap_derive only supports non-tuple structs"), // and enums"),
};
quote!(#inner_impl)
}
pub fn gen_from_argmatches_impl_for_struct(
name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let from_argmatches_fn = gen_from_argmatches_fn_for_struct(name, fields, parent_attribute);
quote! {
impl ::clap::FromArgMatches for #name {
#from_argmatches_fn
}
impl From<::clap::ArgMatches> for #name {
fn from(m: ::clap::ArgMatches) -> Self {
use ::clap::FromArgMatches;
<Self as ::clap::FromArgMatches>::from_argmatches(&m)
}
}
// @TODO impl TryFrom once stable
}
}
pub fn gen_from_argmatches_fn_for_struct(
struct_name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let field_block = gen_constructor(fields, parent_attribute);
quote! {
fn from_argmatches(matches: &::clap::ArgMatches) -> Self {
#struct_name #field_block
}
}
}
pub fn gen_constructor(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let fields = fields.iter().map(|field| {
let attrs = Attrs::from_field(field, parent_attribute.casing());
let field_name = field.ident.as_ref().unwrap();
let kind = attrs.kind();
match &*attrs.kind() {
Kind::Subcommand(ty) => {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let unwrapper = match **ty {
Ty::Option => quote!(),
_ => quote_spanned!( ty.span()=> .unwrap() ),
};
quote_spanned! { kind.span()=>
#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper
}
}
Kind::FlattenStruct => quote_spanned! { kind.span()=>
#field_name: ::clap::FromArgMatches::from_argmatches(matches)
},
Kind::Skip(val) => match val {
None => quote_spanned!(kind.span()=> #field_name: Default::default()),
Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
},
Kind::Arg(ty) => {
use self::ParserKind::*;
let parser = attrs.parser();
let func = &parser.func;
let span = parser.kind.span();
let (value_of, values_of, parse) = match *parser.kind {
FromStr => (
quote_spanned!(span=> value_of),
quote_spanned!(span=> values_of),
func.clone(),
),
TryFromStr => (
quote_spanned!(span=> value_of),
quote_spanned!(span=> values_of),
quote_spanned!(func.span()=> |s| #func(s).unwrap()),
),
FromOsStr => (
quote_spanned!(span=> value_of_os),
quote_spanned!(span=> values_of_os),
func.clone(),
),
TryFromOsStr => (
quote_spanned!(span=> value_of_os),
quote_spanned!(span=> values_of_os),
quote_spanned!(func.span()=> |s| #func(s).unwrap()),
),
FromOccurrences => (
quote_spanned!(span=> occurrences_of),
quote!(),
func.clone(),
),
FromFlag => (quote!(), quote!(), func.clone()),
};
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let name = attrs.cased_name();
let field_value = match **ty {
Ty::Bool => quote_spanned! { ty.span()=>
matches.is_present(#name)
},
Ty::Option => quote_spanned! { ty.span()=>
matches.#value_of(#name)
.map(#parse)
},
Ty::OptionOption => quote_spanned! { ty.span()=>
if matches.is_present(#name) {
Some(matches.#value_of(#name).map(#parse))
} else {
None
}
},
Ty::OptionVec => quote_spanned! { ty.span()=>
if matches.is_present(#name) {
Some(matches.#values_of(#name)
.map(|v| v.map(#parse).collect())
.unwrap_or_else(Vec::new))
} else {
None
}
},
Ty::Vec => quote_spanned! { ty.span()=>
matches.#values_of(#name)
.map(|v| v.map(#parse).collect())
.unwrap_or_else(Vec::new)
},
Ty::Other if occurrences => quote_spanned! { ty.span()=>
#parse(matches.#value_of(#name))
},
Ty::Other if flag => quote_spanned! { ty.span()=>
#parse(matches.is_present(#name))
},
Ty::Other => quote_spanned! { ty.span()=>
matches.#value_of(#name)
.map(#parse)
.unwrap()
},
};
quote_spanned!(field.span()=> #field_name: #field_value )
}
}
});
quote! {{
#( #fields ),*
}}
}
pub fn gen_from_argmatches_impl_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl ::clap::FromArgMatches for #name {
fn from_argmatches(matches: &::clap::ArgMatches) -> Self {
<#name>::from_subcommand(matches.subcommand())
.unwrap()
}
}
impl From<::clap::ArgMatches> for #name {
fn from(m: ::clap::ArgMatches) -> Self {
use ::clap::FromArgMatches;
<Self as ::clap::FromArgMatches>::from_argmatches(&m)
}
}
// @TODO: impl TryFrom once stable
}
}

View file

@ -0,0 +1,132 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use std::env;
use proc_macro2;
use syn;
use super::{spanned::Sp, Attrs, GenOutput, Name, DEFAULT_CASING};
pub fn derive_into_app(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
let inner_impl = match input.data {
Struct(syn::DataStruct { .. }) => {
gen_into_app_impl_for_struct(struct_name, &input.attrs).tokens
}
// @TODO impl into_app for enums?
// Enum(ref e) => clap_for_enum_impl(struct_name, &e.variants, &input.attrs),
_ => panic!("clap_derive only supports non-tuple structs"), // and enums"),
};
quote!(#inner_impl)
}
pub fn gen_into_app_impl_for_struct(name: &syn::Ident, attrs: &[syn::Attribute]) -> GenOutput {
let into_app_fn = gen_into_app_fn_for_struct(attrs);
let into_app_fn_tokens = into_app_fn.tokens;
let tokens = quote! {
impl ::clap::IntoApp for #name {
#into_app_fn_tokens
}
impl<'b> Into<::clap::App<'b>> for #name {
fn into(self) -> ::clap::App<'b> {
use ::clap::IntoApp;
<#name as ::clap::IntoApp>::into_app()
}
}
};
GenOutput {
tokens,
attrs: into_app_fn.attrs,
}
}
pub fn gen_into_app_fn_for_struct(struct_attrs: &[syn::Attribute]) -> GenOutput {
let gen = gen_app_builder(struct_attrs);
let app_tokens = gen.tokens;
let tokens = quote! {
fn into_app<'b>() -> ::clap::App<'b> {
Self::augment_app(#app_tokens)
}
};
GenOutput {
tokens,
attrs: gen.attrs,
}
}
pub fn gen_app_builder(attrs: &[syn::Attribute]) -> GenOutput {
let name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
let attrs = Attrs::from_struct(
proc_macro2::Span::call_site(),
attrs,
Name::Assigned(syn::LitStr::new(&name, proc_macro2::Span::call_site())),
Sp::call_site(DEFAULT_CASING),
);
let tokens = {
let name = attrs.cased_name();
quote!(::clap::App::new(#name))
};
GenOutput { tokens, attrs }
}
pub fn gen_into_app_impl_for_enum(name: &syn::Ident, attrs: &[syn::Attribute]) -> GenOutput {
let into_app_fn = gen_into_app_fn_for_enum(attrs);
let into_app_fn_tokens = into_app_fn.tokens;
let tokens = quote! {
impl ::clap::IntoApp for #name {
#into_app_fn_tokens
}
impl<'b> Into<::clap::App<'b>> for #name {
fn into(self) -> ::clap::App<'b> {
use ::clap::IntoApp;
<#name as ::clap::IntoApp>::into_app()
}
}
};
GenOutput {
tokens,
attrs: into_app_fn.attrs,
}
}
pub fn gen_into_app_fn_for_enum(enum_attrs: &[syn::Attribute]) -> GenOutput {
let gen = gen_app_builder(enum_attrs);
let app_tokens = gen.tokens;
let tokens = quote! {
fn into_app<'b>() -> ::clap::App<'b> {
let app = #app_tokens
.setting(::clap::AppSettings::SubcommandRequiredElseHelp);
Self::augment_app(app)
}
};
GenOutput {
tokens,
attrs: gen.attrs,
}
}

View file

@ -0,0 +1,30 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use syn;
pub mod arg_enum;
pub mod attrs;
pub mod parse;
pub mod spanned;
pub mod ty;
mod clap;
mod from_argmatches;
mod into_app;
pub use self::arg_enum::derive_arg_enum;
pub use self::attrs::{Attrs, Kind, Name, Parser, ParserKind, CasingStyle, GenOutput, DEFAULT_CASING};
pub use self::ty::{sub_type, Ty};
pub use self::clap::derive_clap;
pub use self::from_argmatches::derive_from_argmatches;
pub use self::into_app::derive_into_app;

View file

@ -0,0 +1,268 @@
use std::iter::FromIterator;
use proc_macro2::TokenStream;
use proc_macro_error::{abort, ResultExt};
use syn::{
self, parenthesized,
parse::{Parse, ParseBuffer, ParseStream},
parse2,
punctuated::Punctuated,
spanned::Spanned,
Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token,
};
pub struct ClapAttributes {
pub paren_token: syn::token::Paren,
pub attrs: Punctuated<ClapAttr, Token![,]>,
}
impl Parse for ClapAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let paren_token = parenthesized!(content in input);
let attrs = content.parse_terminated(ClapAttr::parse)?;
Ok(ClapAttributes { paren_token, attrs })
}
}
pub enum ClapAttr {
// single-identifier attributes
Short(Ident),
Long(Ident),
Flatten(Ident),
Subcommand(Ident),
NoVersion(Ident),
// ident [= "string literal"]
About(Ident, Option<LitStr>),
Author(Ident, Option<LitStr>),
// ident = "string literal"
Version(Ident, LitStr),
RenameAll(Ident, LitStr),
NameLitStr(Ident, LitStr),
// parse(parser_kind [= parser_func])
Parse(Ident, ParserSpec),
// ident [= arbitrary_expr]
Skip(Ident, Option<Expr>),
// ident = arbitrary_expr
NameExpr(Ident, Expr),
// ident(arbitrary_expr,*)
MethodCall(Ident, Vec<Expr>),
}
impl Parse for ClapAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
use self::ClapAttr::*;
let name: Ident = input.parse()?;
let name_str = name.to_string();
if input.peek(Token![=]) {
// `name = value` attributes.
let assign_token = input.parse::<Token![=]>()?; // skip '='
if input.peek(LitStr) {
let lit: LitStr = input.parse()?;
let lit_str = lit.value();
let check_empty_lit = |s| {
if lit_str.is_empty() {
abort!(
lit.span(),
"`#[clap({} = \"\")` is deprecated, \
now it's default behavior",
s
);
}
};
match &*name_str.to_string() {
"rename_all" => Ok(RenameAll(name, lit)),
"version" => {
check_empty_lit("version");
Ok(Version(name, lit))
}
"author" => {
check_empty_lit("author");
Ok(Author(name, Some(lit)))
}
"about" => {
check_empty_lit("about");
Ok(About(name, Some(lit)))
}
"skip" => {
let expr = ExprLit {
attrs: vec![],
lit: Lit::Str(lit),
};
let expr = Expr::Lit(expr);
Ok(Skip(name, Some(expr)))
}
_ => Ok(NameLitStr(name, lit)),
}
} else {
match input.parse::<Expr>() {
Ok(expr) => {
if name_str == "skip" {
Ok(Skip(name, Some(expr)))
} else {
Ok(NameExpr(name, expr))
}
}
Err(_) => abort! {
assign_token.span(),
"expected `string literal` or `expression` after `=`"
},
}
}
} else if input.peek(syn::token::Paren) {
// `name(...)` attributes.
let nested;
parenthesized!(nested in input);
match name_str.as_ref() {
"parse" => {
let parser_specs: Punctuated<ParserSpec, Token![,]> =
nested.parse_terminated(ParserSpec::parse)?;
if parser_specs.len() == 1 {
Ok(Parse(name, parser_specs[0].clone()))
} else {
abort!(name.span(), "parse must have exactly one argument")
}
}
"raw" => match nested.parse::<LitBool>() {
Ok(bool_token) => {
let expr = ExprLit {
attrs: vec![],
lit: Lit::Bool(bool_token),
};
let expr = Expr::Lit(expr);
Ok(MethodCall(name, vec![expr]))
}
Err(_) => {
abort!(name.span(),
"`#[clap(raw(...))` attributes are removed, \
they are replaced with raw methods";
help = "if you meant to call `clap::Arg::raw()` method \
you should use bool literal, like `raw(true)` or `raw(false)`";
note = raw_method_suggestion(nested);
);
}
},
_ => {
let method_args: Punctuated<_, Token![,]> =
nested.parse_terminated(Expr::parse)?;
Ok(MethodCall(name, Vec::from_iter(method_args)))
}
}
} else {
// Attributes represented with a sole identifier.
match name_str.as_ref() {
"long" => Ok(Long(name)),
"short" => Ok(Short(name)),
"flatten" => Ok(Flatten(name)),
"subcommand" => Ok(Subcommand(name)),
"no_version" => Ok(NoVersion(name)),
"about" => (Ok(About(name, None))),
"author" => (Ok(Author(name, None))),
"skip" => Ok(Skip(name, None)),
"version" => abort!(
name.span(),
"#[clap(version)] is invalid attribute, \
clap_derive inherits version from Cargo.toml by default, \
no attribute needed"
),
_ => abort!(name.span(), "unexpected attribute: {}", name_str),
}
}
}
}
#[derive(Clone)]
pub struct ParserSpec {
pub kind: Ident,
pub eq_token: Option<Token![=]>,
pub parse_func: Option<Expr>,
}
impl Parse for ParserSpec {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let kind = input
.parse()
.map_err(|_| input.error("parser specification must start with identifier"))?;
let eq_token = input.parse()?;
let parse_func = match eq_token {
None => None,
Some(_) => Some(input.parse()?),
};
Ok(ParserSpec {
kind,
eq_token,
parse_func,
})
}
}
fn raw_method_suggestion(ts: ParseBuffer) -> String {
let do_parse = move || -> Result<(Ident, TokenStream), syn::Error> {
let name = ts.parse()?;
let _eq: Token![=] = ts.parse()?;
let val: LitStr = ts.parse()?;
Ok((name, syn::parse_str(&val.value())?))
};
if let Ok((name, val)) = do_parse() {
let val = val.to_string().replace(" ", "").replace(",", ", ");
format!(
"if you need to call `clap::Arg/App::{}` method you \
can do it like this: #[clap({}({}))]",
name, name, val
)
} else {
"if you need to call some method from `clap::Arg/App` \
you should use raw method, see \
https://docs.rs/structopt/0.3/structopt/#raw-methods"
.into()
}
}
pub fn parse_clap_attributes(all_attrs: &[Attribute]) -> Vec<ClapAttr> {
all_attrs
.iter()
.filter(|attr| attr.path.is_ident("clap"))
.flat_map(|attr| {
let attrs: ClapAttributes = parse2(attr.tokens.clone())
.map_err(|e| match &*e.to_string() {
// this error message is misleading and points to Span::call_site()
// so we patch it with something meaningful
"unexpected end of input, expected parentheses" => {
let span = attr.path.span();
let patch_msg = "expected parentheses after `clap`";
syn::Error::new(span, patch_msg)
}
_ => e,
})
.unwrap_or_abort();
attrs.attrs
})
.collect()
}

View file

@ -0,0 +1,101 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use std::ops::{Deref, DerefMut};
use syn::LitStr;
/// An entity with a span attached.
#[derive(Debug, Clone)]
pub struct Sp<T> {
span: Span,
val: T,
}
impl<T> Sp<T> {
pub fn new(val: T, span: Span) -> Self {
Sp { val, span }
}
pub fn call_site(val: T) -> Self {
Sp {
val,
span: Span::call_site(),
}
}
pub fn span(&self) -> Span {
self.span
}
}
impl<T: ToString> Sp<T> {
pub fn as_ident(&self) -> Ident {
Ident::new(&self.to_string(), self.span)
}
pub fn as_lit(&self) -> LitStr {
LitStr::new(&self.to_string(), self.span)
}
}
impl<T> Deref for Sp<T> {
type Target = T;
fn deref(&self) -> &T {
&self.val
}
}
impl<T> DerefMut for Sp<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.val
}
}
impl From<Ident> for Sp<String> {
fn from(ident: Ident) -> Self {
Sp {
val: ident.to_string(),
span: ident.span(),
}
}
}
impl From<LitStr> for Sp<String> {
fn from(lit: LitStr) -> Self {
Sp {
val: lit.value(),
span: lit.span(),
}
}
}
impl<'a> From<Sp<&'a str>> for Sp<String> {
fn from(sp: Sp<&'a str>) -> Self {
Sp::new(sp.val.into(), sp.span)
}
}
impl<T: PartialEq> PartialEq for Sp<T> {
fn eq(&self, other: &Sp<T>) -> bool {
self.val == other.val
}
}
impl<T: AsRef<str>> AsRef<str> for Sp<T> {
fn as_ref(&self) -> &str {
self.val.as_ref()
}
}
impl<T: ToTokens> ToTokens for Sp<T> {
fn to_tokens(&self, stream: &mut TokenStream) {
// this is the simplest way out of correct ones to change span on
// arbitrary token tree I can come up with
let tt = self.val.to_token_stream().into_iter().map(|mut tt| {
tt.set_span(self.span.clone());
tt
});
stream.extend(tt);
}
}

View file

@ -0,0 +1,108 @@
//! Special types handling
use super::spanned::Sp;
use syn::{
spanned::Spanned, GenericArgument, Path, PathArguments, PathArguments::AngleBracketed,
PathSegment, Type, TypePath,
};
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Ty {
Bool,
Vec,
Option,
OptionOption,
OptionVec,
Other,
}
impl Ty {
pub fn from_syn_ty(ty: &syn::Type) -> Sp<Self> {
use self::Ty::*;
let t = |kind| Sp::new(kind, ty.span());
if is_simple_ty(ty, "bool") {
t(Bool)
} else if is_generic_ty(ty, "Vec") {
t(Vec)
} else if let Some(subty) = subty_if_name(ty, "Option") {
if is_generic_ty(subty, "Option") {
t(OptionOption)
} else if is_generic_ty(subty, "Vec") {
t(OptionVec)
} else {
t(Option)
}
} else {
t(Other)
}
}
}
pub fn sub_type(ty: &syn::Type) -> Option<&syn::Type> {
subty_if(ty, |_| true)
}
fn only_last_segment(ty: &syn::Type) -> Option<&PathSegment> {
match ty {
Type::Path(TypePath {
qself: None,
path:
Path {
leading_colon: None,
segments,
},
}) => only_one(segments.iter()),
_ => None,
}
}
fn subty_if<F>(ty: &syn::Type, f: F) -> Option<&syn::Type>
where
F: FnOnce(&PathSegment) -> bool,
{
only_last_segment(ty)
.filter(|segment| f(segment))
.and_then(|segment| {
if let AngleBracketed(args) = &segment.arguments {
only_one(args.args.iter()).and_then(|genneric| {
if let GenericArgument::Type(ty) = genneric {
Some(ty)
} else {
None
}
})
} else {
None
}
})
}
fn subty_if_name<'a>(ty: &'a syn::Type, name: &str) -> Option<&'a syn::Type> {
subty_if(ty, |seg| seg.ident == name)
}
fn is_simple_ty(ty: &syn::Type, name: &str) -> bool {
only_last_segment(ty)
.map(|segment| {
if let PathArguments::None = segment.arguments {
segment.ident == name
} else {
false
}
})
.unwrap_or(false)
}
fn is_generic_ty(ty: &syn::Type, name: &str) -> bool {
subty_if_name(ty, name).is_some()
}
fn only_one<I, T>(mut iter: I) -> Option<T>
where
I: Iterator<Item = T>,
{
iter.next().filter(|_| iter.next().is_none())
}

61
clap_derive/src/lib.rs Normal file
View file

@ -0,0 +1,61 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
//! This crate is custom derive for clap. It should not be used
//! directly. See [clap documentation](https://docs.rs/clap)
//! for the usage of `#[derive(Clap)]`.
#![recursion_limit = "256"]
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
extern crate heck;
extern crate proc_macro2;
extern crate proc_macro_error;
use proc_macro_error::proc_macro_error;
mod derives;
// /// It is required to have this seperate and specificly defined.
// #[proc_macro_derive(ArgEnum, attributes(case_sensitive))]
// pub fn arg_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// let input: syn::DeriveInput = syn::parse(input).unwrap();
// derives::derive_arg_enum(&input).into()
// }
/// Generates the `Clap` impl.
#[proc_macro_derive(Clap, attributes(clap))]
#[proc_macro_error]
pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
derives::derive_clap(&input).into()
}
/// Generates the `IntoApp` impl.
#[proc_macro_derive(IntoApp, attributes(clap))]
#[proc_macro_error]
pub fn into_app(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
derives::derive_into_app(&input).into()
}
/// Generates the `FromArgMatches` impl.
#[proc_macro_derive(FromArgMatches)]
#[proc_macro_error]
pub fn from_argmatches(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
derives::derive_from_argmatches(&input).into()
}

View file

@ -0,0 +1,54 @@
// // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// // Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
// //
// // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// // option. This file may not be copied, modified, or distributed
// // except according to those terms.
// #[macro_use]
// extern crate clap;
// #[macro_use]
// extern crate clap_derive;
// use clap::{App, Arg};
// #[derive(ArgEnum, Debug, PartialEq)]
// enum ArgChoice {
// Foo,
// Bar,
// Baz,
// }
// #[test]
// fn when_lowercase() {
// let matches = App::new(env!("CARGO_PKG_NAME"))
// .arg(
// Arg::with_name("arg")
// .required(true)
// .takes_value(true)
// .possible_values(&ArgChoice::variants()),
// )
// .try_get_matches_from(vec!["", "foo"])
// .unwrap();
// let t = value_t!(matches.value_of("arg"), ArgChoice);
// assert!(t.is_ok());
// assert_eq!(t.unwrap(), ArgChoice::Foo);
// }
// #[test]
// fn when_capitalized() {
// let matches = App::new(env!("CARGO_PKG_NAME"))
// .arg(
// Arg::with_name("arg")
// .required(true)
// .takes_value(true)
// .possible_values(&ArgChoice::variants()),
// )
// .try_get_matches_from(vec!["", "Foo"])
// .unwrap();
// let t = value_t!(matches.value_of("arg"), ArgChoice);
// assert!(t.is_ok());
// assert_eq!(t.unwrap(), ArgChoice::Foo);
// }

View file

@ -0,0 +1,51 @@
// // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// // Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
// //
// // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// // option. This file may not be copied, modified, or distributed
// // except according to those terms.
// #[macro_use]
// extern crate clap;
// use clap::{App, Arg, ArgEnum};
// #[derive(ArgEnum, Debug, PartialEq)]
// #[case_sensitive]
// enum ArgChoice {
// Foo,
// Bar,
// Baz,
// }
// #[test]
// fn when_lowercase() {
// let matches = App::new(env!("CARGO_PKG_NAME"))
// .arg(
// Arg::with_name("arg")
// .required(true)
// .takes_value(true)
// .possible_values(&ArgChoice::variants()),
// )
// .try_get_matches_from(vec!["", "foo"]); // We expect this to fail.
// assert!(matches.is_err());
// assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::InvalidValue);
// }
// #[test]
// fn when_capitalized() {
// let matches = App::new(env!("CARGO_PKG_NAME"))
// .arg(
// Arg::with_name("arg")
// .required(true)
// .takes_value(true)
// .possible_values(&ArgChoice::variants()),
// )
// .try_get_matches_from(vec!["", "Foo"])
// .unwrap();
// let t = value_t!(matches.value_of("arg"), ArgChoice);
// assert!(t.is_ok());
// assert_eq!(t.unwrap(), ArgChoice::Foo);
// }

View file

@ -0,0 +1,290 @@
use clap::Clap;
#[test]
fn test_single_word_enum_variant_is_default_renamed_into_kebab_case() {
#[derive(Clap, Debug, PartialEq)]
enum Opt {
Command { foo: u32 },
}
assert_eq!(
Opt::Command { foo: 0 },
Opt::parse_from(&["test", "command", "0"])
);
}
#[test]
fn test_multi_word_enum_variant_is_renamed() {
#[derive(Clap, Debug, PartialEq)]
enum Opt {
FirstCommand { foo: u32 },
}
assert_eq!(
Opt::FirstCommand { foo: 0 },
Opt::parse_from(&["test", "first-command", "0"])
);
}
#[test]
fn test_standalone_long_generates_kebab_case() {
#[derive(Clap, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Opt {
#[clap(long)]
FOO_OPTION: bool,
}
assert_eq!(
Opt { FOO_OPTION: true },
Opt::parse_from(&["test", "--foo-option"])
);
}
#[test]
fn test_custom_long_overwrites_default_name() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(long = "foo")]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::parse_from(&["test", "--foo"])
);
}
#[test]
fn test_standalone_long_uses_previous_defined_custom_name() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(name = "foo", long)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::parse_from(&["test", "--foo"])
);
}
#[test]
fn test_standalone_long_ignores_afterwards_defined_custom_name() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(long, name = "foo")]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::parse_from(&["test", "--foo-option"])
);
}
#[test]
fn test_standalone_short_generates_kebab_case() {
#[derive(Clap, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Opt {
#[clap(short)]
FOO_OPTION: bool,
}
assert_eq!(Opt { FOO_OPTION: true }, Opt::parse_from(&["test", "-f"]));
}
#[test]
fn test_custom_short_overwrites_default_name() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(short = "o")]
foo_option: bool,
}
assert_eq!(Opt { foo_option: true }, Opt::parse_from(&["test", "-o"]));
}
#[test]
fn test_standalone_short_uses_previous_defined_custom_name() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(name = "option", short)]
foo_option: bool,
}
assert_eq!(Opt { foo_option: true }, Opt::parse_from(&["test", "-o"]));
}
#[test]
fn test_standalone_short_ignores_afterwards_defined_custom_name() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(short, name = "option")]
foo_option: bool,
}
assert_eq!(Opt { foo_option: true }, Opt::parse_from(&["test", "-f"]));
}
#[test]
fn test_standalone_long_uses_previous_defined_casing() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "screaming_snake", long)]
foo_option: bool,
}
assert_eq!(
Opt { foo_option: true },
Opt::parse_from(&["test", "--FOO_OPTION"])
);
}
#[test]
fn test_standalone_short_uses_previous_defined_casing() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "screaming_snake", short)]
foo_option: bool,
}
assert_eq!(Opt { foo_option: true }, Opt::parse_from(&["test", "-F"]));
}
#[test]
fn test_standalone_long_works_with_verbatim_casing() {
#[derive(Clap, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Opt {
#[clap(rename_all = "verbatim", long)]
_fOO_oPtiON: bool,
}
assert_eq!(
Opt { _fOO_oPtiON: true },
Opt::parse_from(&["test", "--_fOO_oPtiON"])
);
}
#[test]
fn test_standalone_short_works_with_verbatim_casing() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(rename_all = "verbatim", short)]
_foo: bool,
}
assert_eq!(Opt { _foo: true }, Opt::parse_from(&["test", "-_"]));
}
#[test]
fn test_rename_all_is_propagated_from_struct_to_fields() {
#[derive(Clap, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
struct Opt {
#[clap(long)]
foo: bool,
}
assert_eq!(Opt { foo: true }, Opt::parse_from(&["test", "--FOO"]));
}
#[test]
fn test_rename_all_is_not_propagated_from_struct_into_flattened() {
#[derive(Clap, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
struct Opt {
#[clap(flatten)]
foo: Foo,
}
#[derive(Clap, Debug, PartialEq)]
struct Foo {
#[clap(long)]
foo: bool,
}
assert_eq!(
Opt {
foo: Foo { foo: true }
},
Opt::parse_from(&["test", "--foo"])
);
}
#[test]
fn test_rename_all_is_not_propagated_from_struct_into_subcommand() {
#[derive(Clap, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
struct Opt {
#[clap(subcommand)]
foo: Foo,
}
#[derive(Clap, Debug, PartialEq)]
enum Foo {
Command {
#[clap(long)]
foo: bool,
},
}
assert_eq!(
Opt {
foo: Foo::Command { foo: true }
},
Opt::parse_from(&["test", "command", "--foo"])
);
}
#[test]
fn test_rename_all_is_propagated_from_enum_to_variants_and_their_fields() {
#[derive(Clap, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
enum Opt {
FirstVariant,
SecondVariant {
#[clap(long)]
foo: bool,
},
}
assert_eq!(
Opt::FirstVariant,
Opt::parse_from(&["test", "FIRST_VARIANT"])
);
assert_eq!(
Opt::SecondVariant { foo: true },
Opt::parse_from(&["test", "SECOND_VARIANT", "--FOO"])
);
}
#[test]
fn test_rename_all_is_propagation_can_be_overridden() {
#[derive(Clap, Debug, PartialEq)]
#[clap(rename_all = "screaming_snake")]
enum Opt {
#[clap(rename_all = "kebab_case")]
FirstVariant {
#[clap(long)]
foo_option: bool,
},
SecondVariant {
#[clap(rename_all = "kebab_case", long)]
foo_option: bool,
},
}
assert_eq!(
Opt::FirstVariant { foo_option: true },
Opt::parse_from(&["test", "first-variant", "--foo-option"])
);
assert_eq!(
Opt::SecondVariant { foo_option: true },
Opt::parse_from(&["test", "SECOND_VARIANT", "--foo-option"])
);
}

View file

@ -0,0 +1,85 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
#[test]
fn required_argument() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
arg: i32,
}
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test", "42"]));
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn optional_argument() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
arg: Option<i32>,
}
assert_eq!(Opt { arg: Some(42) }, Opt::parse_from(&["test", "42"]));
assert_eq!(Opt { arg: None }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn argument_with_default() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(default_value = "42")]
arg: i32,
}
assert_eq!(Opt { arg: 24 }, Opt::parse_from(&["test", "24"]));
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn arguments() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
arg: Vec<i32>,
}
assert_eq!(Opt { arg: vec![24] }, Opt::parse_from(&["test", "24"]));
assert_eq!(Opt { arg: vec![] }, Opt::parse_from(&["test"]));
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::parse_from(&["test", "24", "42"])
);
}
#[test]
fn arguments_safe() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
arg: Vec<i32>,
}
assert_eq!(
Opt { arg: vec![24] },
Opt::try_parse_from(&["test", "24"]).unwrap()
);
assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::try_parse_from(&["test", "24", "42"]).unwrap()
);
assert_eq!(
clap::ErrorKind::ValueValidation,
Opt::try_parse_from(&["test", "NOPE"]).err().unwrap().kind
);
}

View file

@ -0,0 +1,52 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
mod utils;
use clap::Clap;
use utils::*;
#[test]
fn no_author_version_about() {
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "foo", no_version)]
struct Opt {}
let output = get_long_help::<Opt>();
assert!(output.starts_with("foo \n\nUSAGE:"));
}
#[test]
fn use_env() {
#[derive(Clap, PartialEq, Debug)]
#[clap(author, about)]
struct Opt {}
let output = get_long_help::<Opt>();
assert!(output.starts_with("clap_derive"));
assert!(output.contains("Guillaume Pinot <texitoi@texitoi.eu>, Kevin K. <kbknapp@gmail.com>"));
assert!(output.contains("Parse command line argument by defining a struct, derive crate"));
}
#[test]
fn explicit_version_not_str() {
const VERSION: &str = "custom version";
#[derive(Clap)]
#[clap(version = VERSION)]
pub struct Opt {}
let output = get_long_help::<Opt>();
assert!(output.contains("custom version"));
}

View file

@ -0,0 +1,30 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
#[test]
fn basic() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", long = "arg")]
arg: Vec<i32>,
}
assert_eq!(Opt { arg: vec![24] }, Opt::parse_from(&["test", "-a24"]));
assert_eq!(Opt { arg: vec![] }, Opt::parse_from(&["test"]));
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::parse_from(&["test", "-a24", "--arg", "42"])
);
}

View file

@ -0,0 +1,309 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
use std::ffi::{CString, OsStr, OsString};
use std::num::ParseIntError;
use std::path::PathBuf;
#[derive(Clap, PartialEq, Debug)]
struct PathOpt {
#[clap(short, long, parse(from_os_str))]
path: PathBuf,
#[clap(short, default_value = "../", parse(from_os_str))]
default_path: PathBuf,
#[clap(short, parse(from_os_str))]
vector_path: Vec<PathBuf>,
#[clap(short, parse(from_os_str))]
option_path_1: Option<PathBuf>,
#[clap(short = "q", parse(from_os_str))]
option_path_2: Option<PathBuf>,
}
#[test]
fn test_path_opt_simple() {
assert_eq!(
PathOpt {
path: PathBuf::from("/usr/bin"),
default_path: PathBuf::from("../"),
vector_path: vec![
PathBuf::from("/a/b/c"),
PathBuf::from("/d/e/f"),
PathBuf::from("/g/h/i"),
],
option_path_1: None,
option_path_2: Some(PathBuf::from("j.zip")),
},
PathOpt::parse_from(&[
"test", "-p", "/usr/bin", "-v", "/a/b/c", "-v", "/d/e/f", "-v", "/g/h/i", "-q",
"j.zip",
])
);
}
fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(input, 16)
}
#[derive(Clap, PartialEq, Debug)]
struct HexOpt {
#[clap(short, parse(try_from_str = parse_hex))]
number: u64,
}
#[test]
fn test_parse_hex() {
assert_eq!(
HexOpt { number: 5 },
HexOpt::parse_from(&["test", "-n", "5"])
);
assert_eq!(
HexOpt { number: 0xabcdef },
HexOpt::parse_from(&["test", "-n", "abcdef"])
);
let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err();
assert!(err.message.contains("invalid digit found in string"), err);
}
fn custom_parser_1(_: &str) -> &'static str {
"A"
}
fn custom_parser_2(_: &str) -> Result<&'static str, u32> {
Ok("B")
}
fn custom_parser_3(_: &OsStr) -> &'static str {
"C"
}
fn custom_parser_4(_: &OsStr) -> Result<&'static str, String> {
Ok("D")
}
#[derive(Clap, PartialEq, Debug)]
struct NoOpOpt {
#[clap(short, parse(from_str = custom_parser_1))]
a: &'static str,
#[clap(short, parse(try_from_str = custom_parser_2))]
b: &'static str,
#[clap(short, parse(from_os_str = custom_parser_3))]
c: &'static str,
#[clap(short, parse(try_from_os_str = custom_parser_4))]
d: &'static str,
}
#[test]
fn test_every_custom_parser() {
assert_eq!(
NoOpOpt {
a: "A",
b: "B",
c: "C",
d: "D"
},
NoOpOpt::parse_from(&["test", "-a=?", "-b=?", "-c=?", "-d=?"])
);
}
// Note: can't use `Vec<u8>` directly, as clap would instead look for
// conversion function from `&str` to `u8`.
type Bytes = Vec<u8>;
#[derive(Clap, PartialEq, Debug)]
struct DefaultedOpt {
#[clap(short, parse(from_str))]
bytes: Bytes,
#[clap(short, parse(try_from_str))]
integer: u64,
#[clap(short, parse(from_os_str))]
path: PathBuf,
}
#[test]
fn test_parser_with_default_value() {
assert_eq!(
DefaultedOpt {
bytes: b"E\xc2\xb2=p\xc2\xb2c\xc2\xb2+m\xc2\xb2c\xe2\x81\xb4".to_vec(),
integer: 9000,
path: PathBuf::from("src/lib.rs"),
},
DefaultedOpt::parse_from(&[
"test",
"-b",
"E²=p²c²+m²c⁴",
"-i",
"9000",
"-p",
"src/lib.rs",
])
);
}
#[derive(PartialEq, Debug)]
struct Foo(u8);
fn foo(value: u64) -> Foo {
Foo(value as u8)
}
#[derive(Clap, PartialEq, Debug)]
struct Occurrences {
#[clap(short, long, parse(from_occurrences))]
signed: i32,
#[clap(short, parse(from_occurrences))]
little_signed: i8,
#[clap(short, parse(from_occurrences))]
unsigned: usize,
#[clap(short = "r", parse(from_occurrences))]
little_unsigned: u8,
#[clap(short, long, parse(from_occurrences = foo))]
custom: Foo,
}
#[test]
fn test_parser_occurrences() {
assert_eq!(
Occurrences {
signed: 3,
little_signed: 1,
unsigned: 0,
little_unsigned: 4,
custom: Foo(5),
},
Occurrences::parse_from(&[
"test", "-s", "--signed", "--signed", "-l", "-rrrr", "-cccc", "--custom",
])
);
}
#[test]
fn test_custom_bool() {
fn parse_bool(s: &str) -> Result<bool, String> {
match s {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(format!("invalid bool {}", s)),
}
}
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, parse(try_from_str = parse_bool))]
debug: bool,
#[clap(
short,
default_value = "false",
parse(try_from_str = parse_bool)
)]
verbose: bool,
#[clap(short, parse(try_from_str = parse_bool))]
tribool: Option<bool>,
#[clap(short, parse(try_from_str = parse_bool))]
bitset: Vec<bool>,
}
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "-d"]).is_err());
assert!(Opt::try_parse_from(&["test", "-dfoo"]).is_err());
assert_eq!(
Opt {
debug: false,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::parse_from(&["test", "-dfalse"])
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::parse_from(&["test", "-dtrue"])
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::parse_from(&["test", "-dtrue", "-vfalse"])
);
assert_eq!(
Opt {
debug: true,
verbose: true,
tribool: None,
bitset: vec![],
},
Opt::parse_from(&["test", "-dtrue", "-vtrue"])
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: Some(false),
bitset: vec![],
},
Opt::parse_from(&["test", "-dtrue", "-tfalse"])
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: Some(true),
bitset: vec![],
},
Opt::parse_from(&["test", "-dtrue", "-ttrue"])
);
assert_eq!(
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![false, true, false, false],
},
Opt::parse_from(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"])
);
}
#[test]
fn test_cstring() {
use clap::IntoApp;
#[derive(Clap)]
struct Opt {
#[clap(parse(try_from_str = CString::new))]
c_string: CString,
}
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt::parse_from(&["test", "bla"]).c_string.to_bytes(),
b"bla"
);
assert!(Opt::try_parse_from(&["test", "bla\0bla"]).is_err());
}

View file

@ -0,0 +1,55 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#![deny(warnings)]
extern crate clap;
use clap::Clap;
fn try_str(s: &str) -> Result<String, std::convert::Infallible> {
Ok(s.into())
}
#[test]
fn warning_never_struct() {
#[derive(Debug, PartialEq, Clap)]
struct Opt {
#[clap(parse(try_from_str = try_str))]
s: String,
}
assert_eq!(
Opt {
s: "foo".to_string()
},
Opt::parse_from(&["test", "foo"])
);
}
#[test]
fn warning_never_enum() {
#[derive(Debug, PartialEq, Clap)]
enum Opt {
Foo {
#[clap(parse(try_from_str = try_str))]
s: String,
},
}
assert_eq!(
Opt::Foo {
s: "foo".to_string()
},
Opt::parse_from(&["test", "foo", "foo"])
);
}

View file

@ -0,0 +1,116 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
mod utils;
use clap::Clap;
use utils::*;
#[test]
fn doc_comments() {
/// Lorem ipsum
#[derive(Clap, PartialEq, Debug)]
struct LoremIpsum {
/// Fooify a bar
/// and a baz
#[clap(short, long)]
foo: bool,
}
let help = get_long_help::<LoremIpsum>();
assert!(help.contains("Lorem ipsum"));
assert!(help.contains("Fooify a bar and a baz"));
}
#[test]
fn help_is_better_than_comments() {
/// Lorem ipsum
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
/// Fooify a bar
#[clap(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")]
foo: bool,
}
let help = get_long_help::<LoremIpsum>();
assert!(help.contains("Dolor sit amet"));
assert!(!help.contains("Lorem ipsum"));
assert!(help.contains("DO NOT PASS A BAR"));
}
#[test]
fn empty_line_in_doc_comment_is_double_linefeed() {
/// Foo.
///
/// Bar
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", no_version)]
struct LoremIpsum {}
let help = get_long_help::<LoremIpsum>();
assert!(help.starts_with("lorem-ipsum \nFoo.\n\nBar\n\nUSAGE:"));
}
#[test]
fn field_long_doc_comment_both_help_long_help() {
/// Lorem ipsumclap
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
/// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES.
///
/// Or something else
#[clap(long)]
foo: bool,
}
let short_help = get_help::<LoremIpsum>();
let long_help = get_long_help::<LoremIpsum>();
assert!(short_help.contains("CIRCUMSTANCES"));
assert!(!short_help.contains("CIRCUMSTANCES."));
assert!(!short_help.contains("Or something else"));
assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES"));
assert!(long_help.contains("Or something else"));
}
#[test]
fn top_long_doc_comment_both_help_long_help() {
/// Lorem ipsumclap
#[derive(Clap, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
#[clap(subcommand)]
foo: SubCommand,
}
#[derive(Clap, Debug)]
pub enum SubCommand {
/// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES
///
/// Or something else
Foo {
#[clap(help = "foo")]
bars: Vec<String>,
},
}
let short_help = get_help::<LoremIpsum>();
let long_help = get_subcommand_long_help::<LoremIpsum>("foo");
assert!(!short_help.contains("Or something else"));
assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES"));
assert!(long_help.contains("Or something else"));
}

View file

@ -0,0 +1,32 @@
mod utils;
use clap::Clap;
use utils::*;
#[test]
fn explicit_short_long_no_rename() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = ".", long = ".foo")]
foo: Vec<String>,
}
assert_eq!(
Opt {
foo: vec!["short".into(), "long".into()]
},
Opt::parse_from(&["test", "-.", "short", "--.foo", "long"])
);
}
#[test]
fn explicit_name_no_rename() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(name = ".options")]
foo: Vec<String>,
}
let help = get_long_help::<Opt>();
assert!(help.contains("[.options]..."))
}

145
clap_derive/tests/flags.rs Normal file
View file

@ -0,0 +1,145 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
#[test]
fn unique_flag() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
alice: bool,
}
assert_eq!(Opt { alice: false }, Opt::parse_from(&["test"]));
assert_eq!(Opt { alice: true }, Opt::parse_from(&["test", "-a"]));
assert_eq!(Opt { alice: true }, Opt::parse_from(&["test", "--alice"]));
assert!(Opt::try_parse_from(&["test", "-i"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "-a"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "--alice"]).is_err());
}
#[test]
fn multiple_flag() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, long, parse(from_occurrences))]
alice: u64,
#[clap(short, long, parse(from_occurrences))]
bob: u8,
}
assert_eq!(Opt { alice: 0, bob: 0 }, Opt::parse_from(&["test"]));
assert_eq!(Opt { alice: 1, bob: 0 }, Opt::parse_from(&["test", "-a"]));
assert_eq!(
Opt { alice: 2, bob: 0 },
Opt::parse_from(&["test", "-a", "-a"])
);
assert_eq!(
Opt { alice: 2, bob: 2 },
Opt::parse_from(&["test", "-a", "--alice", "-bb"])
);
assert_eq!(
Opt { alice: 3, bob: 1 },
Opt::parse_from(&["test", "-aaa", "--bob"])
);
assert!(Opt::try_parse_from(&["test", "-i"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err());
}
fn parse_from_flag(b: bool) -> std::sync::atomic::AtomicBool {
std::sync::atomic::AtomicBool::new(b)
}
#[test]
fn non_bool_flags() {
#[derive(Clap, Debug)]
struct Opt {
#[clap(short, long, parse(from_flag = parse_from_flag))]
alice: std::sync::atomic::AtomicBool,
#[clap(short, long, parse(from_flag))]
bob: std::sync::atomic::AtomicBool,
}
let falsey = Opt::parse_from(&["test"]);
assert!(!falsey.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(!falsey.bob.load(std::sync::atomic::Ordering::Relaxed));
let alice = Opt::parse_from(&["test", "-a"]);
assert!(alice.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(!alice.bob.load(std::sync::atomic::Ordering::Relaxed));
let bob = Opt::parse_from(&["test", "-b"]);
assert!(!bob.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(bob.bob.load(std::sync::atomic::Ordering::Relaxed));
let both = Opt::parse_from(&["test", "-b", "-a"]);
assert!(both.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(both.bob.load(std::sync::atomic::Ordering::Relaxed));
}
#[test]
fn combined_flags() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
alice: bool,
#[clap(short, long, parse(from_occurrences))]
bob: u64,
}
assert_eq!(
Opt {
alice: false,
bob: 0
},
Opt::parse_from(&["test"])
);
assert_eq!(
Opt {
alice: true,
bob: 0
},
Opt::parse_from(&["test", "-a"])
);
assert_eq!(
Opt {
alice: true,
bob: 0
},
Opt::parse_from(&["test", "-a"])
);
assert_eq!(
Opt {
alice: false,
bob: 1
},
Opt::parse_from(&["test", "-b"])
);
assert_eq!(
Opt {
alice: true,
bob: 1
},
Opt::parse_from(&["test", "--alice", "--bob"])
);
assert_eq!(
Opt {
alice: true,
bob: 4
},
Opt::parse_from(&["test", "-bb", "-a", "-bb"])
);
}

View file

@ -0,0 +1,99 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
#[test]
fn flatten() {
#[derive(Clap, PartialEq, Debug)]
struct Common {
arg: i32,
}
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(flatten)]
common: Common,
}
assert_eq!(
Opt {
common: Common { arg: 42 }
},
Opt::parse_from(&["test", "42"])
);
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
#[should_panic]
fn flatten_twice() {
#[derive(Clap, PartialEq, Debug)]
struct Common {
arg: i32,
}
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(flatten)]
c1: Common,
// Defines "arg" twice, so this should not work.
#[clap(flatten)]
c2: Common,
}
Opt::parse_from(&["test", "42", "43"]);
}
#[test]
fn flatten_in_subcommand() {
#[derive(Clap, PartialEq, Debug)]
struct Common {
arg: i32,
}
#[derive(Clap, PartialEq, Debug)]
struct Add {
#[clap(short)]
interactive: bool,
#[clap(flatten)]
common: Common,
}
#[derive(Clap, PartialEq, Debug)]
enum Opt {
Fetch {
#[clap(short)]
all: bool,
#[clap(flatten)]
common: Common,
},
Add(Add),
}
assert_eq!(
Opt::Fetch {
all: false,
common: Common { arg: 42 }
},
Opt::parse_from(&["test", "fetch", "42"])
);
assert_eq!(
Opt::Add(Add {
interactive: true,
common: Common { arg: 43 }
}),
Opt::parse_from(&["test", "add", "-i", "43"])
);
}

View file

@ -0,0 +1,50 @@
// https://github.com/TeXitoi/structopt/issues/151
// https://github.com/TeXitoi/structopt/issues/289
#[test]
fn issue_151() {
use clap::{ArgGroup, Clap, IntoApp};
#[derive(Clap, Debug)]
#[clap(group = ArgGroup::with_name("verb").required(true).multiple(true))]
struct Opt {
#[clap(long, group = "verb")]
foo: bool,
#[clap(long, group = "verb")]
bar: bool,
}
#[derive(Debug, Clap)]
struct Cli {
#[clap(flatten)]
a: Opt,
}
assert!(Cli::try_parse_from(&["test"]).is_err());
assert!(Cli::try_parse_from(&["test", "--foo"]).is_ok());
assert!(Cli::try_parse_from(&["test", "--bar"]).is_ok());
assert!(Cli::try_parse_from(&["test", "--zebra"]).is_err());
}
#[test]
fn issue_289() {
use clap::{AppSettings, Clap, IntoApp};
#[derive(Clap)]
#[clap(setting = AppSettings::InferSubcommands)]
enum Args {
SomeCommand(SubSubCommand),
AnotherCommand,
}
#[derive(Clap)]
#[clap(setting = AppSettings::InferSubcommands)]
enum SubSubCommand {
TestCommand,
}
assert!(Args::try_parse_from(&["test", "some-command", "test-command"]).is_ok());
assert!(Args::try_parse_from(&["test", "some", "test-command"]).is_ok());
assert!(Args::try_parse_from(&["test", "some-command", "test"]).is_ok());
assert!(Args::try_parse_from(&["test", "some", "test"]).is_ok());
}

View file

@ -0,0 +1,13 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
#[rustversion::attr(any(not(stable), before(1.39)), ignore)]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}

View file

@ -0,0 +1,196 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
force: bool,
#[clap(short, long, parse(from_occurrences))]
verbose: u64,
#[clap(subcommand)]
cmd: Sub,
}
#[derive(Clap, PartialEq, Debug)]
enum Sub {
Fetch {},
Add {},
}
#[derive(Clap, PartialEq, Debug)]
struct Opt2 {
#[clap(short, long)]
force: bool,
#[clap(short, long, parse(from_occurrences))]
verbose: u64,
#[clap(subcommand)]
cmd: Option<Sub>,
}
#[test]
fn test_no_cmd() {
let result = Opt::try_parse_from(&["test"]);
assert!(result.is_err());
assert_eq!(
Opt2 {
force: false,
verbose: 0,
cmd: None
},
Opt2::parse_from(&["test"])
);
}
#[test]
fn test_fetch() {
assert_eq!(
Opt {
force: false,
verbose: 3,
cmd: Sub::Fetch {}
},
Opt::parse_from(&["test", "-vvv", "fetch"])
);
assert_eq!(
Opt {
force: true,
verbose: 0,
cmd: Sub::Fetch {}
},
Opt::parse_from(&["test", "--force", "fetch"])
);
}
#[test]
fn test_add() {
assert_eq!(
Opt {
force: false,
verbose: 0,
cmd: Sub::Add {}
},
Opt::parse_from(&["test", "add"])
);
assert_eq!(
Opt {
force: false,
verbose: 2,
cmd: Sub::Add {}
},
Opt::parse_from(&["test", "-vv", "add"])
);
}
#[test]
fn test_badinput() {
let result = Opt::try_parse_from(&["test", "badcmd"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "add", "--verbose"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "--badopt", "add"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "add", "--badopt"]);
assert!(result.is_err());
}
#[derive(Clap, PartialEq, Debug)]
struct Opt3 {
#[clap(short, long)]
all: bool,
#[clap(subcommand)]
cmd: Sub2,
}
#[derive(Clap, PartialEq, Debug)]
enum Sub2 {
Foo {
file: String,
#[clap(subcommand)]
cmd: Sub3,
},
Bar {},
}
#[derive(Clap, PartialEq, Debug)]
enum Sub3 {
Baz {},
Quux {},
}
#[test]
fn test_subsubcommand() {
assert_eq!(
Opt3 {
all: true,
cmd: Sub2::Foo {
file: "lib.rs".to_string(),
cmd: Sub3::Quux {}
}
},
Opt3::parse_from(&["test", "--all", "foo", "lib.rs", "quux"])
);
}
#[derive(Clap, PartialEq, Debug)]
enum SubSubCmdWithOption {
Remote {
#[clap(subcommand)]
cmd: Option<Remote>,
},
Stash {
#[clap(subcommand)]
cmd: Stash,
},
}
#[derive(Clap, PartialEq, Debug)]
enum Remote {
Add { name: String, url: String },
Remove { name: String },
}
#[derive(Clap, PartialEq, Debug)]
enum Stash {
Save,
Pop,
}
#[test]
fn sub_sub_cmd_with_option() {
fn make(args: &[&str]) -> Option<SubSubCmdWithOption> {
use clap::{FromArgMatches, IntoApp};
SubSubCmdWithOption::try_parse_from(args).ok()
}
assert_eq!(
Some(SubSubCmdWithOption::Remote { cmd: None }),
make(&["", "remote"])
);
assert_eq!(
Some(SubSubCmdWithOption::Remote {
cmd: Some(Remote::Add {
name: "origin".into(),
url: "http".into()
})
}),
make(&["", "remote", "add", "origin", "http"])
);
assert_eq!(
Some(SubSubCmdWithOption::Stash { cmd: Stash::Save }),
make(&["", "stash", "save"])
);
assert_eq!(None, make(&["", "stash"]));
}

View file

@ -0,0 +1,149 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::{AppSettings, Clap};
use std::num::ParseIntError;
pub const DISPLAY_ORDER: usize = 2;
// Check if the global settings compile
#[derive(Clap, Debug, PartialEq, Eq)]
#[clap(global_setting = AppSettings::ColoredHelp)]
struct Opt {
#[clap(
long = "x",
display_order = DISPLAY_ORDER,
next_line_help = true,
default_value = "0",
require_equals = true
)]
x: i32,
#[clap(short = "l", long = "level", aliases = &["set-level", "lvl"])]
level: String,
#[clap(long("values"))]
values: Vec<i32>,
#[clap(name = "FILE", requires_if("FILE", "values"))]
files: Vec<String>,
}
#[test]
fn test_slice() {
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::parse_from(&["test", "-l", "1"])
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::parse_from(&["test", "--level", "1"])
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::parse_from(&["test", "--set-level", "1"])
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::parse_from(&["test", "--lvl", "1"])
);
}
#[test]
fn test_multi_args() {
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: vec!["file".to_string()],
values: vec![],
},
Opt::parse_from(&["test", "-l", "1", "file"])
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: vec!["FILE".to_string()],
values: vec![1],
},
Opt::parse_from(&["test", "-l", "1", "--values", "1", "--", "FILE"])
);
}
#[test]
fn test_multi_args_fail() {
let result = Opt::try_parse_from(&["test", "-l", "1", "--", "FILE"]);
assert!(result.is_err());
}
#[test]
fn test_bool() {
assert_eq!(
Opt {
x: 1,
level: "1".to_string(),
files: vec![],
values: vec![],
},
Opt::parse_from(&["test", "-l", "1", "--x=1"])
);
let result = Opt::try_parse_from(&["test", "-l", "1", "--x", "1"]);
assert!(result.is_err());
}
fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(input, 16)
}
#[derive(Clap, PartialEq, Debug)]
struct HexOpt {
#[clap(short = "n", parse(try_from_str = parse_hex))]
number: u64,
}
#[test]
fn test_parse_hex_function_path() {
assert_eq!(
HexOpt { number: 5 },
HexOpt::parse_from(&["test", "-n", "5"])
);
assert_eq!(
HexOpt { number: 0xabcdef },
HexOpt::parse_from(&["test", "-n", "abcdef"])
);
let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err();
assert!(err.message.contains("invalid digit found in string"), err);
}

View file

@ -0,0 +1,285 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
#[test]
fn required_option() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
arg: i32,
}
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test", "-a42"]));
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test", "-a", "42"]));
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test", "--arg", "42"]));
assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn optional_option() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<i32>,
}
assert_eq!(Opt { arg: Some(42) }, Opt::parse_from(&["test", "-a42"]));
assert_eq!(Opt { arg: None }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn option_with_default() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, default_value = "42")]
arg: i32,
}
assert_eq!(Opt { arg: 24 }, Opt::parse_from(&["test", "-a24"]));
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn option_with_raw_default() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, default_value = "42")]
arg: i32,
}
assert_eq!(Opt { arg: 24 }, Opt::parse_from(&["test", "-a24"]));
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn options() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, long)]
arg: Vec<i32>,
}
assert_eq!(Opt { arg: vec![24] }, Opt::parse_from(&["test", "-a24"]));
assert_eq!(Opt { arg: vec![] }, Opt::parse_from(&["test"]));
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::parse_from(&["test", "-a24", "--arg", "42"])
);
}
#[test]
fn default_value() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short, default_value = "test")]
arg: String,
}
assert_eq!(Opt { arg: "test".into() }, Opt::parse_from(&["test"]));
assert_eq!(
Opt { arg: "foo".into() },
Opt::parse_from(&["test", "-afoo"])
);
}
#[test]
fn option_from_str() {
#[derive(Debug, PartialEq)]
struct A;
impl<'a> From<&'a str> for A {
fn from(_: &str) -> A {
A
}
}
#[derive(Debug, Clap, PartialEq)]
struct Opt {
#[clap(parse(from_str))]
a: Option<A>,
}
assert_eq!(Opt { a: None }, Opt::parse_from(&["test"]));
assert_eq!(Opt { a: Some(A) }, Opt::parse_from(&["test", "foo"]));
}
#[test]
fn optional_argument_for_optional_option() {
use clap::IntoApp;
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
#[allow(clippy::option_option)]
arg: Option<Option<i32>>,
}
assert_eq!(
Opt {
arg: Some(Some(42))
},
Opt::parse_from(&["test", "-a42"])
);
assert_eq!(Opt { arg: Some(None) }, Opt::parse_from(&["test", "-a"]));
assert_eq!(Opt { arg: None }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn two_option_options() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Option<i32>>,
#[clap(long)]
field: Option<Option<String>>,
}
assert_eq!(
Opt {
arg: Some(Some(42)),
field: Some(Some("f".into()))
},
Opt::parse_from(&["test", "-a42", "--field", "f"])
);
assert_eq!(
Opt {
arg: Some(Some(42)),
field: Some(None)
},
Opt::parse_from(&["test", "-a42", "--field"])
);
assert_eq!(
Opt {
arg: Some(None),
field: Some(None)
},
Opt::parse_from(&["test", "-a", "--field"])
);
assert_eq!(
Opt {
arg: Some(None),
field: Some(Some("f".into()))
},
Opt::parse_from(&["test", "-a", "--field", "f"])
);
assert_eq!(
Opt {
arg: None,
field: Some(None)
},
Opt::parse_from(&["test", "--field"])
);
assert_eq!(
Opt {
arg: None,
field: None
},
Opt::parse_from(&["test"])
);
}
#[test]
fn optional_vec() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Vec<i32>>,
}
assert_eq!(
Opt { arg: Some(vec![1]) },
Opt::parse_from(&["test", "-a", "1"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a2"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a2", "-a"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a", "-a2"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a", "1", "2"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2, 3])
},
Opt::parse_from(&["test", "-a", "1", "2", "-a", "3"])
);
assert_eq!(Opt { arg: Some(vec![]) }, Opt::parse_from(&["test", "-a"]));
assert_eq!(
Opt { arg: Some(vec![]) },
Opt::parse_from(&["test", "-a", "-a"])
);
assert_eq!(Opt { arg: None }, Opt::parse_from(&["test"]));
}
#[test]
fn two_optional_vecs() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Vec<i32>>,
#[clap(short)]
b: Option<Vec<i32>>,
}
assert_eq!(
Opt {
arg: Some(vec![1]),
b: Some(vec![])
},
Opt::parse_from(&["test", "-a", "1", "-b"])
);
assert_eq!(
Opt {
arg: Some(vec![1]),
b: Some(vec![])
},
Opt::parse_from(&["test", "-a", "-b", "-a1"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2]),
b: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a2", "-b1", "-b2"])
);
assert_eq!(Opt { arg: None, b: None }, Opt::parse_from(&["test"]));
}

View file

@ -0,0 +1,36 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
mod options {
use clap::Clap;
#[derive(Debug, Clap)]
pub struct Options {
#[clap(subcommand)]
pub subcommand: super::subcommands::SubCommand,
}
}
mod subcommands {
use clap::Clap;
#[derive(Debug, Clap)]
pub enum SubCommand {
/// foo
Foo {
/// foo
bars: Vec<String>,
},
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[test]
fn raw_bool_literal() {
#[derive(Clap, Debug, PartialEq)]
#[clap(no_version, name = "raw_bool")]
struct Opt {
#[clap(raw(false))]
a: String,
#[clap(raw(true))]
b: String,
}
assert_eq!(
Opt {
a: "one".into(),
b: "--help".into()
},
Opt::parse_from(&["test", "one", "--", "--help"])
);
}

View file

@ -0,0 +1,17 @@
use clap::Clap;
#[test]
fn raw_idents() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(short, long)]
r#type: Vec<String>,
}
assert_eq!(
Opt {
r#type: vec!["long".into(), "short".into()]
},
Opt::parse_from(&["test", "--type", "long", "-t", "short"])
);
}

148
clap_derive/tests/skip.rs Normal file
View file

@ -0,0 +1,148 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[test]
fn skip_1() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(short)]
x: u32,
#[clap(skip)]
s: u32,
}
assert!(Opt::try_parse_from(&["test", "-x", "10", "20"]).is_err());
assert_eq!(
Opt::parse_from(&["test", "-x", "10"]),
Opt {
x: 10,
s: 0, // default
}
);
}
#[test]
fn skip_2() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(short)]
x: u32,
#[clap(skip)]
ss: String,
#[clap(skip)]
sn: u8,
y: u32,
#[clap(skip)]
sz: u16,
t: u32,
}
assert_eq!(
Opt::parse_from(&["test", "-x", "10", "20", "30"]),
Opt {
x: 10,
ss: String::from(""),
sn: 0,
y: 20,
sz: 0,
t: 30,
}
);
}
#[test]
fn skip_enum() {
#[derive(Debug, PartialEq)]
#[allow(unused)]
enum Kind {
A,
B,
}
impl Default for Kind {
fn default() -> Self {
return Kind::B;
}
}
#[derive(Clap, Debug, PartialEq)]
pub struct Opt {
#[clap(long, short)]
number: u32,
#[clap(skip)]
k: Kind,
#[clap(skip)]
v: Vec<u32>,
}
assert_eq!(
Opt::parse_from(&["test", "-n", "10"]),
Opt {
number: 10,
k: Kind::B,
v: vec![],
}
);
}
#[test]
fn skip_help_doc_comments() {
#[derive(Clap, Debug, PartialEq)]
pub struct Opt {
#[clap(skip, help = "internal_stuff")]
a: u32,
#[clap(skip, long_help = "internal_stuff\ndo not touch")]
b: u32,
/// Not meant to be used by clap.
///
/// I want a default here.
#[clap(skip)]
c: u32,
#[clap(short, parse(try_from_str))]
n: u32,
}
assert_eq!(
Opt::parse_from(&["test", "-n", "10"]),
Opt {
n: 10,
a: 0,
b: 0,
c: 0,
}
);
}
#[test]
fn skip_val() {
#[derive(Clap, Debug, PartialEq)]
pub struct Opt {
#[clap(long, short)]
number: u32,
#[clap(skip = "key")]
k: String,
#[clap(skip = vec![1, 2, 3])]
v: Vec<u32>,
}
assert_eq!(
Opt::parse_from(&["test", "-n", "10"]),
Opt {
number: 10,
k: "key".into(),
v: vec![1, 2, 3]
}
);
}

View file

@ -0,0 +1,73 @@
//! Checks that types like `::std::option::Option` are not special
use clap::Clap;
#[rustversion::since(1.37)]
#[test]
fn special_types_bool() {
mod inner {
#[allow(non_camel_case_types)]
#[derive(PartialEq, Debug)]
pub struct bool(pub String);
impl std::str::FromStr for self::bool {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(self::bool(s.into()))
}
}
};
#[derive(Clap, PartialEq, Debug)]
struct Opt {
arg: inner::bool,
}
assert_eq!(
Opt {
arg: inner::bool("success".into())
},
Opt::parse_from(&["test", "success"])
);
}
#[test]
fn special_types_option() {
fn parser(s: &str) -> Option<String> {
Some(s.to_string())
}
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(parse(from_str = parser))]
arg: ::std::option::Option<String>,
}
assert_eq!(
Opt {
arg: Some("success".into())
},
Opt::parse_from(&["test", "success"])
);
}
#[test]
fn special_types_vec() {
fn parser(s: &str) -> Vec<String> {
vec![s.to_string()]
}
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(parse(from_str = parser))]
arg: ::std::vec::Vec<String>,
}
assert_eq!(
Opt {
arg: vec!["success".into()]
},
Opt::parse_from(&["test", "success"])
);
}

View file

@ -0,0 +1,209 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
mod utils;
use clap::Clap;
use utils::*;
#[derive(Clap, PartialEq, Debug)]
enum Opt {
/// Fetch stuff from GitHub
Fetch {
#[clap(long)]
all: bool,
#[clap(short, long)]
/// Overwrite local branches.
force: bool,
repo: String,
},
Add {
#[clap(short, long)]
interactive: bool,
#[clap(short, long)]
verbose: bool,
},
}
#[test]
fn test_fetch() {
assert_eq!(
Opt::Fetch {
all: true,
force: false,
repo: "origin".to_string()
},
Opt::parse_from(&["test", "fetch", "--all", "origin"])
);
assert_eq!(
Opt::Fetch {
all: false,
force: true,
repo: "origin".to_string()
},
Opt::parse_from(&["test", "fetch", "-f", "origin"])
);
}
#[test]
fn test_add() {
assert_eq!(
Opt::Add {
interactive: false,
verbose: false
},
Opt::parse_from(&["test", "add"])
);
assert_eq!(
Opt::Add {
interactive: true,
verbose: true
},
Opt::parse_from(&["test", "add", "-i", "-v"])
);
}
#[test]
fn test_no_parse() {
let result = Opt::try_parse_from(&["test", "badcmd", "-i", "-v"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "add", "--badoption"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test"]);
assert!(result.is_err());
}
#[derive(Clap, PartialEq, Debug)]
enum Opt2 {
DoSomething { arg: String },
}
#[test]
/// This test is specifically to make sure that hyphenated subcommands get
/// processed correctly.
fn test_hyphenated_subcommands() {
assert_eq!(
Opt2::DoSomething {
arg: "blah".to_string()
},
Opt2::parse_from(&["test", "do-something", "blah"])
);
}
#[derive(Clap, PartialEq, Debug)]
enum Opt3 {
Add,
Init,
Fetch,
}
#[test]
fn test_null_commands() {
assert_eq!(Opt3::Add, Opt3::parse_from(&["test", "add"]));
assert_eq!(Opt3::Init, Opt3::parse_from(&["test", "init"]));
assert_eq!(Opt3::Fetch, Opt3::parse_from(&["test", "fetch"]));
}
#[derive(Clap, PartialEq, Debug)]
#[clap(about = "Not shown")]
struct Add {
file: String,
}
/// Not shown
#[derive(Clap, PartialEq, Debug)]
struct Fetch {
remote: String,
}
#[derive(Clap, PartialEq, Debug)]
enum Opt4 {
// Not shown
/// Add a file
Add(Add),
Init,
/// download history from remote
Fetch(Fetch),
}
#[test]
fn test_tuple_commands() {
use clap::IntoApp;
assert_eq!(
Opt4::Add(Add {
file: "f".to_string()
}),
Opt4::parse_from(&["test", "add", "f"])
);
assert_eq!(Opt4::Init, Opt4::parse_from(&["test", "init"]));
assert_eq!(
Opt4::Fetch(Fetch {
remote: "origin".to_string()
}),
Opt4::parse_from(&["test", "fetch", "origin"])
);
let output = get_long_help::<Opt4>();
assert!(output.contains("download history from remote"));
assert!(output.contains("Add a file"));
assert!(!output.contains("Not shown"));
}
#[test]
fn enum_in_enum_subsubcommand() {
#[derive(Clap, Debug, PartialEq)]
pub enum Opt {
Daemon(DaemonCommand),
}
#[derive(Clap, Debug, PartialEq)]
pub enum DaemonCommand {
Start,
Stop,
}
let result = Opt::try_parse_from(&["test"]);
assert!(result.is_err());
let result = Opt::try_parse_from(&["test", "daemon"]);
assert!(result.is_err());
let result = Opt::parse_from(&["test", "daemon", "start"]);
assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
}
#[test]
fn flatten_enum() {
#[derive(Clap, Debug, PartialEq)]
struct Opt {
#[clap(flatten)]
sub_cmd: SubCmd,
}
#[derive(Clap, Debug, PartialEq)]
enum SubCmd {
Foo,
Bar,
}
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt::parse_from(&["test", "foo"]),
Opt {
sub_cmd: SubCmd::Foo
}
);
}

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(short, default_value = true)]
b: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: default_value is meaningless for bool
--> $DIR/bool_default_value.rs:14:19
|
14 | #[clap(short, default_value = true)]
| ^^^^^^^^^^^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(short, required = true)]
b: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: required is meaningless for bool
--> $DIR/bool_required.rs:14:19
|
14 | #[clap(short, required = true)]
| ^^^^^^^^

View file

@ -0,0 +1,22 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap]
debug: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: expected parentheses after `clap`
--> $DIR/clap_derive_empty_attr.rs:14:7
|
14 | #[clap]
| ^^^^

View file

@ -0,0 +1,22 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap = "short"]
debug: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: expected parentheses
--> $DIR/clap_derive_name_value_attr.rs:14:12
|
14 | #[clap = "short"]
| ^

View file

@ -0,0 +1,30 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
struct DaemonOpts {
#[clap(short)]
user: String,
#[clap(short)]
group: String,
}
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
/// Opts.
#[clap(flatten)]
opts: DaemonOpts,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: methods and doc comments are not allowed for flattened entry
--> $DIR/flatten_and_doc.rs:23:12
|
23 | #[clap(flatten)]
| ^^^^^^^

View file

@ -0,0 +1,29 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
struct DaemonOpts {
#[clap(short)]
user: String,
#[clap(short)]
group: String,
}
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(short, flatten)]
opts: DaemonOpts,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: methods and doc comments are not allowed for flattened entry
--> $DIR/flatten_and_methods.rs:22:19
|
22 | #[clap(short, flatten)]
| ^^^^^^^

View file

@ -0,0 +1,29 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
struct DaemonOpts {
#[clap(short)]
user: String,
#[clap(short)]
group: String,
}
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(flatten, parse(from_occurrences))]
opts: DaemonOpts,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: parse attribute is not allowed for flattened entry
--> $DIR/flatten_and_parse.rs:22:21
|
22 | #[clap(flatten, parse(from_occurrences))]
| ^^^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(short, non_existing_attribute = 1)]
debug: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error[E0599]: no method named `non_existing_attribute` found for type `clap::build::arg::Arg<'_>` in the current scope
--> $DIR/non_existent_attr.rs:14:19
|
14 | #[clap(short, non_existing_attribute = 1)]
| ^^^^^^^^^^^^^^^^^^^^^^ method not found in `clap::build::arg::Arg<'_>`

View file

@ -0,0 +1,20 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
n: Option<Option<u32>>,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: Option<Option<T>> type is meaningless for positional argument
--> $DIR/opt_opt_nonpositional.rs:14:8
|
14 | n: Option<Option<u32>>,
| ^^^^^^

View file

@ -0,0 +1,20 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
n: Option<Vec<u32>>,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: Option<Vec<T>> type is meaningless for positional argument
--> $DIR/opt_vec_nonpositional.rs:14:8
|
14 | n: Option<Vec<u32>>,
| ^^^^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(short, default_value = 1)]
n: Option<u32>,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: default_value is meaningless for Option
--> $DIR/option_default_value.rs:14:19
|
14 | #[clap(short, default_value = 1)]
| ^^^^^^^^^^^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(short, required = true)]
n: Option<u32>,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: required is meaningless for Option
--> $DIR/option_required.rs:14:19
|
14 | #[clap(short, required = true)]
| ^^^^^^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(parse(try_from_os_str))]
s: String,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: you must set parser for `try_from_os_str` explicitly
--> $DIR/parse_empty_try_from_os.rs:14:18
|
14 | #[clap(parse(try_from_os_str))]
| ^^^^^^^^^^^^^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(parse(from_str = "2"))]
s: String,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: `parse` argument must be a function path
--> $DIR/parse_function_is_not_path.rs:14:29
|
14 | #[clap(parse(from_str = "2"))]
| ^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(parse("from_str"))]
s: String,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: parser specification must start with identifier
--> $DIR/parse_literal_spec.rs:14:18
|
14 | #[clap(parse("from_str"))]
| ^^^^^^^^^^

View file

@ -0,0 +1,21 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "basic")]
struct Opt {
#[clap(parse(from_str, from_str))]
s: String,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: parse must have exactly one argument
--> $DIR/parse_not_zero_args.rs:14:12
|
14 | #[clap(parse(from_str, from_str))]
| ^^^^^

Some files were not shown because too many files have changed in this diff Show more