mirror of
https://github.com/clap-rs/clap
synced 2025-01-18 15:43:54 +00:00
Merge remote-tracking branch 'origin-structopt/master'
This commit is contained in:
commit
61edf0f2cc
39 changed files with 4059 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
|
*~
|
17
.travis.yml
Normal file
17
.travis.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
language: rust
|
||||||
|
cache: cargo
|
||||||
|
rust:
|
||||||
|
- 1.21.0
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- rust: nightly
|
||||||
|
env: FEATURES="--features nightly"
|
||||||
|
- rust: stable
|
||||||
|
env: RUN=FMT
|
||||||
|
before_script: rustup component add rustfmt-preview
|
||||||
|
script: cargo fmt --all -- --write-mode diff
|
||||||
|
script:
|
||||||
|
- cargo test $FEATURES
|
156
CHANGELOG.md
Normal file
156
CHANGELOG.md
Normal 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
|
||||||
|
#[structopt(short = "v", long = "verbose")]
|
||||||
|
verbose: u64,
|
||||||
|
```
|
||||||
|
must be changed by
|
||||||
|
```rust
|
||||||
|
#[structopt(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 `#[structopt(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)
|
32
Cargo.toml
Normal file
32
Cargo.toml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "structopt"
|
||||||
|
version = "0.2.10"
|
||||||
|
authors = ["Guillaume Pinot <texitoi@texitoi.eu>", "others"]
|
||||||
|
description = "Parse command line argument by defining a struct."
|
||||||
|
documentation = "https://docs.rs/structopt"
|
||||||
|
repository = "https://github.com/TeXitoi/structopt"
|
||||||
|
keywords = ["clap", "cli", "derive", "docopt"]
|
||||||
|
categories = ["command-line-interface"]
|
||||||
|
license = "Apache-2.0/MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["clap/default"]
|
||||||
|
nightly = ["structopt-derive/nightly"]
|
||||||
|
suggestions = ["clap/suggestions"]
|
||||||
|
color = ["clap/color"]
|
||||||
|
wrap_help = ["clap/wrap_help"]
|
||||||
|
yaml = ["clap/yaml"]
|
||||||
|
lints = ["clap/lints"]
|
||||||
|
debug = ["clap/debug"]
|
||||||
|
no_cargo = ["clap/no_cargo"]
|
||||||
|
doc = ["clap/doc"]
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
travis-ci = { repository = "TeXitoi/structopt" }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "2.20", default-features = false }
|
||||||
|
structopt-derive = { path = "structopt-derive", version = "0.2.10" }
|
||||||
|
|
||||||
|
[workspace]
|
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal 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
LICENSE-MIT
Normal file
21
LICENSE-MIT
Normal 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.
|
133
README.md
Normal file
133
README.md
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
# StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt/badge.svg)](https://docs.rs/structopt)
|
||||||
|
|
||||||
|
Parse command line argument by defining a struct. It combines [clap](https://crates.io/crates/clap) with custom derive.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Find it on [Docs.rs](https://docs.rs/structopt). You can also check the [examples](https://github.com/TeXitoi/structopt/tree/master/examples) and the [changelog](https://github.com/TeXitoi/structopt/blob/master/CHANGELOG.md).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Add `structopt` to your dependencies of your `Cargo.toml`:
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
structopt = "0.2"
|
||||||
|
```
|
||||||
|
|
||||||
|
And then, in your rust file:
|
||||||
|
```rust
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
/// A basic example
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(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.
|
||||||
|
/// Activate debug mode
|
||||||
|
#[structopt(short = "d", long = "debug")]
|
||||||
|
debug: bool,
|
||||||
|
|
||||||
|
// The number of occurences of the `v/verbose` flag
|
||||||
|
/// Verbose mode (-v, -vv, -vvv, etc.)
|
||||||
|
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||||
|
verbose: u8,
|
||||||
|
|
||||||
|
/// Set speed
|
||||||
|
#[structopt(short = "s", long = "speed", default_value = "42")]
|
||||||
|
speed: f64,
|
||||||
|
|
||||||
|
/// Output file
|
||||||
|
#[structopt(short = "o", long = "output", parse(from_os_str))]
|
||||||
|
output: PathBuf,
|
||||||
|
|
||||||
|
/// Number of cars
|
||||||
|
#[structopt(short = "c", long = "nb-cars")]
|
||||||
|
nb_cars: Option<i32>,
|
||||||
|
|
||||||
|
/// admin_level to consider
|
||||||
|
#[structopt(short = "l", long = "level")]
|
||||||
|
level: Vec<String>,
|
||||||
|
|
||||||
|
/// Files to process
|
||||||
|
#[structopt(name = "FILE", parse(from_os_str))]
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
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.2.0
|
||||||
|
Guillaume Pinot <texitoi@texitoi.eu>
|
||||||
|
A basic example
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
basic [FLAGS] [OPTIONS] --output <output> [--] [FILE]...
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-d, --debug Activate debug mode
|
||||||
|
-h, --help Prints help information
|
||||||
|
-V, --version Prints version information
|
||||||
|
-v, --verbose Verbose mode
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-c, --nb-cars <nb_cars> Number of cars
|
||||||
|
-l, --level <level>... admin_level to consider
|
||||||
|
-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, output: "foo.txt", car: 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, output: "foo.txt", nb_cars: Some(4), level: ["alice", "bob"], files: ["bar.txt", "baz.txt"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## StructOpt rustc version policy
|
||||||
|
|
||||||
|
- Minimum rustc version modification must be specified in the [changelog](https://github.com/TeXitoi/structopt/blob/master/CHANGELOG.md) and in the [travis configuration](https://github.com/TeXitoi/structopt/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 StructOpt's depedencies (`cargo update` will not fail on StructOpt).
|
||||||
|
- Contributors can increment minimum rustc version if the library user experience is improved.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
I use [docopt](https://crates.io/crates/docopt) since 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.
|
||||||
|
|
15
examples/at_least_two.rs
Normal file
15
examples/at_least_two.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(raw(required = "true", min_values = "2"))]
|
||||||
|
foos: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
54
examples/basic.rs
Normal file
54
examples/basic.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
/// A basic example
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(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.
|
||||||
|
/// Activate debug mode
|
||||||
|
#[structopt(short = "d", long = "debug")]
|
||||||
|
debug: bool,
|
||||||
|
|
||||||
|
// The number of occurences of the `v/verbose` flag
|
||||||
|
/// Verbose mode (-v, -vv, -vvv, etc.)
|
||||||
|
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||||
|
verbose: u8,
|
||||||
|
|
||||||
|
/// Set speed
|
||||||
|
#[structopt(short = "s", long = "speed", default_value = "42")]
|
||||||
|
speed: f64,
|
||||||
|
|
||||||
|
/// Output file
|
||||||
|
#[structopt(short = "o", long = "output", parse(from_os_str))]
|
||||||
|
output: PathBuf,
|
||||||
|
|
||||||
|
/// Number of cars
|
||||||
|
#[structopt(short = "c", long = "nb-cars")]
|
||||||
|
nb_cars: Option<i32>,
|
||||||
|
|
||||||
|
/// admin_level to consider
|
||||||
|
#[structopt(short = "l", long = "level")]
|
||||||
|
level: Vec<String>,
|
||||||
|
|
||||||
|
/// Files to process
|
||||||
|
#[structopt(name = "FILE", parse(from_os_str))]
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
54
examples/deny_missing_docs.rs
Normal file
54
examples/deny_missing_docs.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// This should be in tests but it will not work until
|
||||||
|
// https://github.com/rust-lang/rust/issues/24584 is fixed
|
||||||
|
|
||||||
|
//! A test to check that structopt compiles with deny(missing_docs)
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
/// The options
|
||||||
|
#[derive(StructOpt, Debug, PartialEq)]
|
||||||
|
pub struct Opt {
|
||||||
|
#[structopt(short = "v")]
|
||||||
|
verbose: bool,
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
cmd: Option<Cmd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some subcommands
|
||||||
|
#[derive(StructOpt, Debug, PartialEq)]
|
||||||
|
pub enum Cmd {
|
||||||
|
/// command A
|
||||||
|
A,
|
||||||
|
/// command B
|
||||||
|
B {
|
||||||
|
/// Alice?
|
||||||
|
#[structopt(short = "a")]
|
||||||
|
alice: bool,
|
||||||
|
},
|
||||||
|
/// command C
|
||||||
|
C(COpt),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The options for C
|
||||||
|
#[derive(StructOpt, Debug, PartialEq)]
|
||||||
|
pub struct COpt {
|
||||||
|
#[structopt(short = "b")]
|
||||||
|
bob: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("{:?}", Opt::from_args());
|
||||||
|
}
|
27
examples/enum_in_args.rs
Normal file
27
examples/enum_in_args.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate clap;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
arg_enum! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Baz {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
FooBar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
/// Important argument.
|
||||||
|
#[structopt(raw(possible_values = "&Baz::variants()", case_insensitive = "true"))]
|
||||||
|
i: Baz,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
27
examples/enum_tuple.rs
Normal file
27
examples/enum_tuple.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
pub struct Foo {
|
||||||
|
pub bar: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
pub enum Command {
|
||||||
|
#[structopt(name = "foo")]
|
||||||
|
Foo(Foo),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
#[structopt(name = "classify")]
|
||||||
|
pub struct ApplicationArguments {
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = ApplicationArguments::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
38
examples/example.rs
Normal file
38
examples/example.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(name = "example", about = "An example of StructOpt usage.")]
|
||||||
|
struct Opt {
|
||||||
|
/// A flag, true if used in the command line.
|
||||||
|
#[structopt(short = "d", long = "debug", help = "Activate debug mode")]
|
||||||
|
debug: bool,
|
||||||
|
|
||||||
|
/// An argument of type float, with a default value.
|
||||||
|
#[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")]
|
||||||
|
speed: f64,
|
||||||
|
|
||||||
|
/// Needed parameter, the first on the command line.
|
||||||
|
#[structopt(help = "Input file")]
|
||||||
|
input: String,
|
||||||
|
|
||||||
|
/// An optional parameter, will be `None` if not present on the
|
||||||
|
/// command line.
|
||||||
|
#[structopt(help = "Output file, stdout if not present")]
|
||||||
|
output: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
25
examples/flatten.rs
Normal file
25
examples/flatten.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct Cmdline {
|
||||||
|
#[structopt(short = "v", help = "switch on verbosity")]
|
||||||
|
verbose: bool,
|
||||||
|
#[structopt(flatten)]
|
||||||
|
daemon_opts: DaemonOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct DaemonOpts {
|
||||||
|
#[structopt(short = "u", help = "daemon user")]
|
||||||
|
user: String,
|
||||||
|
#[structopt(short = "g", help = "daemon group")]
|
||||||
|
group: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Cmdline::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
40
examples/git.rs
Normal file
40
examples/git.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
//! `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 the
|
||||||
|
//! `about` attribute.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(name = "git")]
|
||||||
|
/// the stupid content tracker
|
||||||
|
enum Opt {
|
||||||
|
#[structopt(name = "fetch")]
|
||||||
|
/// fetch branches from remote repository
|
||||||
|
Fetch {
|
||||||
|
#[structopt(long = "dry-run")]
|
||||||
|
dry_run: bool,
|
||||||
|
#[structopt(long = "all")]
|
||||||
|
all: bool,
|
||||||
|
#[structopt(default_value = "origin")]
|
||||||
|
repository: String,
|
||||||
|
},
|
||||||
|
#[structopt(name = "add")]
|
||||||
|
/// add files to the staging area
|
||||||
|
Add {
|
||||||
|
#[structopt(short = "i")]
|
||||||
|
interactive: bool,
|
||||||
|
#[structopt(short = "a")]
|
||||||
|
all: bool,
|
||||||
|
files: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = Opt::from_args();
|
||||||
|
|
||||||
|
println!("{:?}", matches);
|
||||||
|
}
|
40
examples/group.rs
Normal file
40
examples/group.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// A functional translation of the example at
|
||||||
|
// https://docs.rs/clap/2.31.2/clap/struct.App.html#method.group
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::clap::ArgGroup;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
// This function is not needed, we can insert everything in the group
|
||||||
|
// attribute, but, as it might be long, using a function is more
|
||||||
|
// lisible.
|
||||||
|
fn vers_arg_group() -> ArgGroup<'static> {
|
||||||
|
// As the attributes of the struct are executed before the struct
|
||||||
|
// fields, we can't use .args(...), but we can use the group
|
||||||
|
// attribute on the fields.
|
||||||
|
ArgGroup::with_name("vers").required(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(raw(group = "vers_arg_group()"))]
|
||||||
|
struct Opt {
|
||||||
|
/// set the version manually
|
||||||
|
#[structopt(long = "set-ver", group = "vers")]
|
||||||
|
set_ver: Option<String>,
|
||||||
|
/// auto increase major
|
||||||
|
#[structopt(long = "major", group = "vers")]
|
||||||
|
major: bool,
|
||||||
|
/// auto increase minor
|
||||||
|
#[structopt(long = "minor", group = "vers")]
|
||||||
|
minor: bool,
|
||||||
|
/// auto increase patch
|
||||||
|
#[structopt(long = "patch", group = "vers")]
|
||||||
|
patch: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
28
examples/keyvalue.rs
Normal file
28
examples/keyvalue.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<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(StructOpt, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "D", parse(try_from_str = "parse_key_val"))]
|
||||||
|
defines: Vec<(String, i32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
20
examples/no_version.rs
Normal file
20
examples/no_version.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::clap::AppSettings;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(
|
||||||
|
name = "no_version",
|
||||||
|
about = "",
|
||||||
|
version = "",
|
||||||
|
author = "",
|
||||||
|
raw(global_settings = "&[AppSettings::DisableVersion]")
|
||||||
|
)]
|
||||||
|
struct Opt {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
39
examples/raw_attributes.rs
Normal file
39
examples/raw_attributes.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::clap::AppSettings;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
/// An example of raw attributes
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(
|
||||||
|
raw(global_settings = "&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]")
|
||||||
|
)]
|
||||||
|
struct Opt {
|
||||||
|
/// Output file
|
||||||
|
#[structopt(short = "o", long = "output")]
|
||||||
|
output: String,
|
||||||
|
|
||||||
|
/// admin_level to consider
|
||||||
|
#[structopt(short = "l", long = "level", raw(aliases = r#"&["set-level", "lvl"]"#))]
|
||||||
|
level: Vec<String>,
|
||||||
|
|
||||||
|
/// Files to process
|
||||||
|
///
|
||||||
|
/// `level` is required if a file is called `FILE`.
|
||||||
|
#[structopt(name = "FILE", raw(requires_if = r#""FILE", "level""#))]
|
||||||
|
files: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
31
examples/simple_group.rs
Normal file
31
examples/simple_group.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
/// Set a custom HTTP verb
|
||||||
|
#[structopt(long = "method", group = "verb")]
|
||||||
|
method: Option<String>,
|
||||||
|
/// HTTP GET; default if no other HTTP verb is selected
|
||||||
|
#[structopt(long = "get", group = "verb")]
|
||||||
|
get: bool,
|
||||||
|
/// HTTP HEAD
|
||||||
|
#[structopt(long = "head", group = "verb")]
|
||||||
|
head: bool,
|
||||||
|
/// HTTP POST
|
||||||
|
#[structopt(long = "post", group = "verb")]
|
||||||
|
post: bool,
|
||||||
|
/// HTTP PUT
|
||||||
|
#[structopt(long = "put", group = "verb")]
|
||||||
|
put: bool,
|
||||||
|
/// HTTP DELETE
|
||||||
|
#[structopt(long = "delete", group = "verb")]
|
||||||
|
delete: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
22
examples/subcommand_aliases.rs
Normal file
22
examples/subcommand_aliases.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::clap::AppSettings;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
// https://docs.rs/clap/2/clap/enum.AppSettings.html#variant.InferSubcommands
|
||||||
|
#[structopt(raw(setting = "AppSettings::InferSubcommands"))]
|
||||||
|
enum Opt {
|
||||||
|
// https://docs.rs/clap/2/clap/struct.App.html#method.alias
|
||||||
|
#[structopt(name = "foo", alias = "foobar")]
|
||||||
|
Foo,
|
||||||
|
// https://docs.rs/clap/2/clap/struct.App.html#method.aliases
|
||||||
|
#[structopt(name = "bar", raw(aliases = r#"&["baz", "fizz"]"#))]
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
println!("{:?}", opt);
|
||||||
|
}
|
445
src/lib.rs
Normal file
445
src/lib.rs
Normal file
|
@ -0,0 +1,445 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! This crate defines the `StructOpt` trait and its custom derrive.
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//!
|
||||||
|
//! If you want to disable all the `clap` features (colors,
|
||||||
|
//! suggestions, ..) add `default-features = false` to the `structopt`
|
||||||
|
//! dependency:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies]
|
||||||
|
//! structopt = { version = "0.2", default-features = false }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## How to `derive(StructOpt)`
|
||||||
|
//!
|
||||||
|
//! First, let's look at an example:
|
||||||
|
//!
|
||||||
|
//! ```should_panic
|
||||||
|
//! #[macro_use]
|
||||||
|
//! extern crate structopt;
|
||||||
|
//!
|
||||||
|
//! use std::path::PathBuf;
|
||||||
|
//! use structopt::StructOpt;
|
||||||
|
//!
|
||||||
|
//! #[derive(Debug, StructOpt)]
|
||||||
|
//! #[structopt(name = "example", about = "An example of StructOpt usage.")]
|
||||||
|
//! struct Opt {
|
||||||
|
//! /// Activate debug mode
|
||||||
|
//! #[structopt(short = "d", long = "debug")]
|
||||||
|
//! debug: bool,
|
||||||
|
//! /// Set speed
|
||||||
|
//! #[structopt(short = "s", long = "speed", default_value = "42")]
|
||||||
|
//! speed: f64,
|
||||||
|
//! /// Input file
|
||||||
|
//! #[structopt(parse(from_os_str))]
|
||||||
|
//! input: PathBuf,
|
||||||
|
//! /// Output file, stdout if not present
|
||||||
|
//! #[structopt(parse(from_os_str))]
|
||||||
|
//! output: Option<PathBuf>,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! let opt = Opt::from_args();
|
||||||
|
//! println!("{:?}", opt);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! So `derive(StructOpt)` tells Rust to generate a command line parser,
|
||||||
|
//! and the various `structopt` attributes are simply
|
||||||
|
//! used for additional parameters.
|
||||||
|
//!
|
||||||
|
//! First, define a struct, whatever its name. This structure will
|
||||||
|
//! correspond to a `clap::App`. Every method of `clap::App` in the
|
||||||
|
//! form of `fn function_name(self, &str)` can be use through attributes
|
||||||
|
//! placed on the struct. In our example above, the `about` attribute
|
||||||
|
//! will become an `.about("An example of StructOpt usage.")` call on the
|
||||||
|
//! generated `clap::App`. There are a few attributes that will default
|
||||||
|
//! if not specified:
|
||||||
|
//!
|
||||||
|
//! - `name`: The binary name displayed in help messages. Defaults
|
||||||
|
//! to the crate name given by Cargo.
|
||||||
|
//! - `version`: Defaults to the crate version given by Cargo.
|
||||||
|
//! - `author`: Defaults to the crate author name given by Cargo.
|
||||||
|
//! - `about`: Defaults to the crate description given by Cargo.
|
||||||
|
//!
|
||||||
|
//! Methods from `clap::App` that don't take an `&str` can be called by
|
||||||
|
//! wrapping them in `raw()`, e.g. to activate colored help text:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! #[macro_use]
|
||||||
|
//! extern crate structopt;
|
||||||
|
//!
|
||||||
|
//! use structopt::StructOpt;
|
||||||
|
//!
|
||||||
|
//! #[derive(StructOpt, Debug)]
|
||||||
|
//! #[structopt(raw(setting = "structopt::clap::AppSettings::ColoredHelp"))]
|
||||||
|
//! struct Opt {
|
||||||
|
//! #[structopt(short = "s")]
|
||||||
|
//! speed: bool,
|
||||||
|
//! #[structopt(short = "d")]
|
||||||
|
//! debug: bool,
|
||||||
|
//! }
|
||||||
|
//! # fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Then, each field of the struct not marked as a subcommand corresponds
|
||||||
|
//! to a `clap::Arg`. As with the struct attributes, every method of
|
||||||
|
//! `clap::Arg` in the form of `fn function_name(self, &str)` can be used
|
||||||
|
//! through specifying it as an attribute.
|
||||||
|
//! The `name` attribute can be used to customize the
|
||||||
|
//! `Arg::with_name()` call (defaults to the field name).
|
||||||
|
//! For functions that do not take a `&str` as argument, the attribute can be
|
||||||
|
//! wrapped in `raw()`, e. g. `raw(aliases = r#"&["alias"]"#, next_line_help = "true")`.
|
||||||
|
//!
|
||||||
|
//! The type of the field gives the kind of argument:
|
||||||
|
//!
|
||||||
|
//! Type | Effect | Added method call to `clap::Arg`
|
||||||
|
//! ---------------------|---------------------------------------------------|--------------------------------------
|
||||||
|
//! `bool` | `true` if the flag is present | `.takes_value(false).multiple(false)`
|
||||||
|
//! `Option<T: FromStr>` | optional positional argument or option | `.takes_value(true).multiple(false)`
|
||||||
|
//! `Vec<T: FromStr>` | list of options or the other positional arguments | `.takes_value(true).multiple(true)`
|
||||||
|
//! `T: FromStr` | required option or positional argument | `.takes_value(true).multiple(false).required(!has_default)`
|
||||||
|
//!
|
||||||
|
//! The `FromStr` trait is used to convert the argument to the given
|
||||||
|
//! type, and the `Arg::validator` method is set to a method using
|
||||||
|
//! `to_string()` (`FromStr::Err` must implement `std::fmt::Display`).
|
||||||
|
//! If you would like to use a custom string parser other than `FromStr`, see
|
||||||
|
//! the [same titled section](#custom-string-parsers) below.
|
||||||
|
//!
|
||||||
|
//! Thus, the `speed` argument is generated as:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # extern crate clap;
|
||||||
|
//! # fn parse_validator<T>(_: String) -> Result<(), String> { unimplemented!() }
|
||||||
|
//! # fn main() {
|
||||||
|
//! clap::Arg::with_name("speed")
|
||||||
|
//! .takes_value(true)
|
||||||
|
//! .multiple(false)
|
||||||
|
//! .required(false)
|
||||||
|
//! .validator(parse_validator::<f64>)
|
||||||
|
//! .short("s")
|
||||||
|
//! .long("speed")
|
||||||
|
//! .help("Set speed")
|
||||||
|
//! .default_value("42");
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Help messages
|
||||||
|
//!
|
||||||
|
//! Help messages for the whole binary or individual arguments can be
|
||||||
|
//! specified using the `about` attribute on the struct and the `help`
|
||||||
|
//! attribute on the field, as we've already seen. For convenience,
|
||||||
|
//! they can also be specified using doc comments. For example:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[macro_use] extern crate structopt;
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! #[structopt(name = "foo")]
|
||||||
|
//! /// The help message that will be displayed when passing `--help`.
|
||||||
|
//! struct Foo {
|
||||||
|
//! #[structopt(short = "b")]
|
||||||
|
//! /// The description for the arg that will be displayed when passing `--help`.
|
||||||
|
//! bar: String
|
||||||
|
//! }
|
||||||
|
//! # fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Subcommands
|
||||||
|
//!
|
||||||
|
//! Some applications, especially large ones, split their functionality
|
||||||
|
//! through the use of "subcommands". Each of these act somewhat like a separate
|
||||||
|
//! command, but is part of the larger group.
|
||||||
|
//! One example is `git`, which has subcommands such as `add`, `commit`,
|
||||||
|
//! and `clone`, to mention just a few.
|
||||||
|
//!
|
||||||
|
//! `clap` has this functionality, and `structopt` supports it through enums:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[macro_use] extern crate structopt;
|
||||||
|
//! # use std::path::PathBuf;
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! #[structopt(name = "git", about = "the stupid content tracker")]
|
||||||
|
//! enum Git {
|
||||||
|
//! #[structopt(name = "add")]
|
||||||
|
//! Add {
|
||||||
|
//! #[structopt(short = "i")]
|
||||||
|
//! interactive: bool,
|
||||||
|
//! #[structopt(short = "p")]
|
||||||
|
//! patch: bool,
|
||||||
|
//! #[structopt(parse(from_os_str))]
|
||||||
|
//! files: Vec<PathBuf>
|
||||||
|
//! },
|
||||||
|
//! #[structopt(name = "fetch")]
|
||||||
|
//! Fetch {
|
||||||
|
//! #[structopt(long = "dry-run")]
|
||||||
|
//! dry_run: bool,
|
||||||
|
//! #[structopt(long = "all")]
|
||||||
|
//! all: bool,
|
||||||
|
//! repository: Option<String>
|
||||||
|
//! },
|
||||||
|
//! #[structopt(name = "commit")]
|
||||||
|
//! Commit {
|
||||||
|
//! #[structopt(short = "m")]
|
||||||
|
//! message: Option<String>,
|
||||||
|
//! #[structopt(short = "a")]
|
||||||
|
//! all: bool
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Using `derive(StructOpt)` on an enum instead of a struct will produce
|
||||||
|
//! a `clap::App` that only takes subcommands. So `git add`, `git fetch`,
|
||||||
|
//! and `git commit` would be commands allowed for the above example.
|
||||||
|
//!
|
||||||
|
//! `structopt` also provides support for applications where certain flags
|
||||||
|
//! need to apply to all subcommands, as well as nested subcommands:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[macro_use] extern crate structopt;
|
||||||
|
//! # fn main() {}
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! #[structopt(name = "make-cookie")]
|
||||||
|
//! struct MakeCookie {
|
||||||
|
//! #[structopt(name = "supervisor", default_value = "Puck", long = "supervisor")]
|
||||||
|
//! supervising_faerie: String,
|
||||||
|
//! #[structopt(name = "tree")]
|
||||||
|
//! /// The faerie tree this cookie is being made in.
|
||||||
|
//! tree: Option<String>,
|
||||||
|
//! #[structopt(subcommand)] // Note that we mark a field as a subcommand
|
||||||
|
//! cmd: Command
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! enum Command {
|
||||||
|
//! #[structopt(name = "pound")]
|
||||||
|
//! /// Pound acorns into flour for cookie dough.
|
||||||
|
//! Pound {
|
||||||
|
//! acorns: u32
|
||||||
|
//! },
|
||||||
|
//! #[structopt(name = "sparkle")]
|
||||||
|
//! /// Add magical sparkles -- the secret ingredient!
|
||||||
|
//! Sparkle {
|
||||||
|
//! #[structopt(short = "m", parse(from_occurrences))]
|
||||||
|
//! magicality: u64,
|
||||||
|
//! #[structopt(short = "c")]
|
||||||
|
//! color: String
|
||||||
|
//! },
|
||||||
|
//! #[structopt(name = "finish")]
|
||||||
|
//! Finish(Finish),
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Subcommand can also be externalized by using a 1-uple enum variant
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! struct Finish {
|
||||||
|
//! #[structopt(short = "t")]
|
||||||
|
//! time: u32,
|
||||||
|
//! #[structopt(subcommand)] // Note that we mark a field as a subcommand
|
||||||
|
//! finish_type: FinishType
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // subsubcommand!
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! enum FinishType {
|
||||||
|
//! #[structopt(name = "glaze")]
|
||||||
|
//! Glaze {
|
||||||
|
//! applications: u32
|
||||||
|
//! },
|
||||||
|
//! #[structopt(name = "powder")]
|
||||||
|
//! Powder {
|
||||||
|
//! flavor: String,
|
||||||
|
//! dips: u32
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Marking a field with `structopt(subcommand)` will add the subcommands of the
|
||||||
|
//! designated enum to the current `clap::App`. The designated enum *must* also
|
||||||
|
//! be derived `StructOpt`. So the above example would take the following
|
||||||
|
//! commands:
|
||||||
|
//!
|
||||||
|
//! + `make-cookie pound 50`
|
||||||
|
//! + `make-cookie sparkle -mmm --color "green"`
|
||||||
|
//! + `make-cookie finish 130 glaze 3`
|
||||||
|
//!
|
||||||
|
//! ### Optional subcommands
|
||||||
|
//!
|
||||||
|
//! A nested subcommand can be marked optional:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[macro_use] extern crate structopt;
|
||||||
|
//! # fn main() {}
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! #[structopt(name = "foo")]
|
||||||
|
//! struct Foo {
|
||||||
|
//! file: String,
|
||||||
|
//! #[structopt(subcommand)]
|
||||||
|
//! cmd: Option<Command>
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! enum Command {
|
||||||
|
//! Bar,
|
||||||
|
//! Baz,
|
||||||
|
//! Quux
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Flattening
|
||||||
|
//!
|
||||||
|
//! It can sometimes be useful to group related arguments in a substruct,
|
||||||
|
//! while keeping the command-line interface flat. In these cases you can mark
|
||||||
|
//! a field as `flatten` and give it another type that derives `StructOpt`:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[macro_use] extern crate structopt;
|
||||||
|
//! # use structopt::StructOpt;
|
||||||
|
//! # fn main() {}
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! struct Cmdline {
|
||||||
|
//! #[structopt(short = "v", help = "switch on verbosity")]
|
||||||
|
//! verbose: bool,
|
||||||
|
//! #[structopt(flatten)]
|
||||||
|
//! daemon_opts: DaemonOpts,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! struct DaemonOpts {
|
||||||
|
//! #[structopt(short = "u", help = "daemon user")]
|
||||||
|
//! user: String,
|
||||||
|
//! #[structopt(short = "g", help = "daemon group")]
|
||||||
|
//! group: String,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! In this example, the derived `Cmdline` parser will support the options `-v`,
|
||||||
|
//! `-u` and `-g`.
|
||||||
|
//!
|
||||||
|
//! This feature also makes it possible to define a `StructOpt` struct in a
|
||||||
|
//! library, parse the corresponding arguments in the main argument parser, and
|
||||||
|
//! pass off this struct to a handler provided by that library.
|
||||||
|
//!
|
||||||
|
//! ## Custom string parsers
|
||||||
|
//!
|
||||||
|
//! If the field type does not have a `FromStr` implementation, or you would
|
||||||
|
//! like to provide a custom parsing scheme other than `FromStr`, you may
|
||||||
|
//! provide a custom string parser using `parse(...)` like this:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[macro_use] extern crate structopt;
|
||||||
|
//! # fn main() {}
|
||||||
|
//! use std::num::ParseIntError;
|
||||||
|
//! use std::path::PathBuf;
|
||||||
|
//!
|
||||||
|
//! fn parse_hex(src: &str) -> Result<u32, ParseIntError> {
|
||||||
|
//! u32::from_str_radix(src, 16)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(StructOpt)]
|
||||||
|
//! struct HexReader {
|
||||||
|
//! #[structopt(short = "n", parse(try_from_str = "parse_hex"))]
|
||||||
|
//! number: u32,
|
||||||
|
//! #[structopt(short = "o", parse(from_os_str))]
|
||||||
|
//! output: PathBuf,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! There are five kinds of custom parsers:
|
||||||
|
//!
|
||||||
|
//! | Kind | Signature | Default |
|
||||||
|
//! |-------------------|---------------------------------------|---------------------------------|
|
||||||
|
//! | `from_str` | `fn(&str) -> T` | `::std::convert::From::from` |
|
||||||
|
//! | `try_from_str` | `fn(&str) -> Result<T, E>` | `::std::str::FromStr::from_str` |
|
||||||
|
//! | `from_os_str` | `fn(&OsStr) -> T` | `::std::convert::From::from` |
|
||||||
|
//! | `try_from_os_str` | `fn(&OsStr) -> Result<T, OsString>` | (no default function) |
|
||||||
|
//! | `from_occurrences`| `fn(u64) -> T` | `value as T` |
|
||||||
|
//!
|
||||||
|
//! The `from_occurrences` parser is special. Using `parse(from_occurrences)`
|
||||||
|
//! results in the _number of flags occurrences_ being stored in the relevant
|
||||||
|
//! field or being passed to the supplied function. In other words, it converts
|
||||||
|
//! something like `-vvv` to `3`. This is equivalent to
|
||||||
|
//! `.takes_value(false).multiple(true)`. Note that the default parser can only
|
||||||
|
//! be used with fields of integer types (`u8`, `usize`, `i64`, etc.).
|
||||||
|
//!
|
||||||
|
//! When supplying a custom string parser, `bool` will not be treated specially:
|
||||||
|
//!
|
||||||
|
//! Type | Effect | Added method call to `clap::Arg`
|
||||||
|
//! ------------|-------------------|--------------------------------------
|
||||||
|
//! `Option<T>` | optional argument | `.takes_value(true).multiple(false)`
|
||||||
|
//! `Vec<T>` | list of arguments | `.takes_value(true).multiple(true)`
|
||||||
|
//! `T` | required argument | `.takes_value(true).multiple(false).required(!has_default)`
|
||||||
|
//!
|
||||||
|
//! In the `try_from_*` variants, the function will run twice on valid input:
|
||||||
|
//! once to validate, and once to parse. Hence, make sure the function is
|
||||||
|
//! side-effect-free.
|
||||||
|
|
||||||
|
extern crate clap as _clap;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt_derive;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use structopt_derive::*;
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
/// Re-export of clap
|
||||||
|
pub mod clap {
|
||||||
|
pub use _clap::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct that is converted from command line arguments.
|
||||||
|
pub trait StructOpt {
|
||||||
|
/// Returns the corresponding `clap::App`.
|
||||||
|
fn clap<'a, 'b>() -> clap::App<'a, 'b>;
|
||||||
|
|
||||||
|
/// Creates the struct from `clap::ArgMatches`. It cannot fail
|
||||||
|
/// with a parameter generated by `clap` by construction.
|
||||||
|
fn from_clap(&clap::ArgMatches) -> Self;
|
||||||
|
|
||||||
|
/// Gets the struct from the command line arguments. Print the
|
||||||
|
/// error message and quit the program in case of failure.
|
||||||
|
fn from_args() -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Self::from_clap(&Self::clap().get_matches())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the struct from any iterator such as a `Vec` of your making.
|
||||||
|
/// Print the error message and quit the program in case of failure.
|
||||||
|
fn from_iter<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
I: IntoIterator,
|
||||||
|
I::Item: Into<OsString> + Clone,
|
||||||
|
{
|
||||||
|
Self::from_clap(&Self::clap().get_matches_from(iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the struct from any iterator such as a `Vec` of your making.
|
||||||
|
///
|
||||||
|
/// Returns a `clap::Error` in case of failure. This does *not* exit in the
|
||||||
|
/// case of `--help` or `--version`, to achieve the same behavior as
|
||||||
|
/// `from_iter()` you must call `.exit()` on the error value.
|
||||||
|
fn from_iter_safe<I>(iter: I) -> Result<Self, clap::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
I: IntoIterator,
|
||||||
|
I::Item: Into<OsString> + Clone,
|
||||||
|
{
|
||||||
|
Ok(Self::from_clap(&Self::clap().get_matches_from_safe(iter)?))
|
||||||
|
}
|
||||||
|
}
|
24
structopt-derive/Cargo.toml
Normal file
24
structopt-derive/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "structopt-derive"
|
||||||
|
version = "0.2.10"
|
||||||
|
authors = ["Guillaume Pinot <texitoi@texitoi.eu>"]
|
||||||
|
description = "Parse command line argument by defining a struct, derive crate."
|
||||||
|
documentation = "https://docs.rs/structopt-derive"
|
||||||
|
repository = "https://github.com/TeXitoi/structopt"
|
||||||
|
keywords = ["clap", "cli", "derive", "docopt"]
|
||||||
|
categories = ["command-line-interface"]
|
||||||
|
license = "Apache-2.0/MIT"
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
travis-ci = { repository = "TeXitoi/structopt" }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "0.14"
|
||||||
|
quote = "0.6"
|
||||||
|
proc-macro2 = "0.4"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
nightly = ["proc-macro2/nightly"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
201
structopt-derive/LICENSE-APACHE
Normal file
201
structopt-derive/LICENSE-APACHE
Normal 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
structopt-derive/LICENSE-MIT
Normal file
21
structopt-derive/LICENSE-MIT
Normal 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.
|
377
structopt-derive/src/attrs.rs
Normal file
377
structopt-derive/src/attrs.rs
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
// 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 proc_macro2::{Span, TokenStream};
|
||||||
|
use std::{env, mem};
|
||||||
|
use syn::Type::Path;
|
||||||
|
use syn::{self, Attribute, Ident, LitStr, MetaList, MetaNameValue, TypePath};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum Kind {
|
||||||
|
Arg(Ty),
|
||||||
|
Subcommand(Ty),
|
||||||
|
FlattenStruct,
|
||||||
|
}
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum Ty {
|
||||||
|
Bool,
|
||||||
|
Vec,
|
||||||
|
Option,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Attrs {
|
||||||
|
name: String,
|
||||||
|
methods: Vec<Method>,
|
||||||
|
parser: (Parser, TokenStream),
|
||||||
|
has_custom_parser: bool,
|
||||||
|
kind: Kind,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Method {
|
||||||
|
name: String,
|
||||||
|
args: TokenStream,
|
||||||
|
}
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Parser {
|
||||||
|
FromStr,
|
||||||
|
TryFromStr,
|
||||||
|
FromOsStr,
|
||||||
|
TryFromOsStr,
|
||||||
|
FromOccurrences,
|
||||||
|
}
|
||||||
|
impl ::std::str::FromStr for Parser {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"from_str" => Ok(Parser::FromStr),
|
||||||
|
"try_from_str" => Ok(Parser::TryFromStr),
|
||||||
|
"from_os_str" => Ok(Parser::FromOsStr),
|
||||||
|
"try_from_os_str" => Ok(Parser::TryFromOsStr),
|
||||||
|
"from_occurrences" => Ok(Parser::FromOccurrences),
|
||||||
|
_ => Err(format!("unsupported parser {}", s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attrs {
|
||||||
|
fn new(name: String) -> Attrs {
|
||||||
|
Attrs {
|
||||||
|
name: name,
|
||||||
|
methods: vec![],
|
||||||
|
parser: (Parser::TryFromStr, quote!(::std::str::FromStr::from_str)),
|
||||||
|
has_custom_parser: false,
|
||||||
|
kind: Kind::Arg(Ty::Other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn push_str_method(&mut self, name: &str, arg: &str) {
|
||||||
|
match (name, arg) {
|
||||||
|
("about", "") | ("version", "") | ("author", "") => {
|
||||||
|
let methods = mem::replace(&mut self.methods, vec![]);
|
||||||
|
self.methods = methods.into_iter().filter(|m| m.name != name).collect();
|
||||||
|
}
|
||||||
|
("name", new_name) => self.name = new_name.into(),
|
||||||
|
(name, arg) => self.methods.push(Method {
|
||||||
|
name: name.to_string(),
|
||||||
|
args: quote!(#arg),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn push_attrs(&mut self, attrs: &[Attribute]) {
|
||||||
|
use Lit::*;
|
||||||
|
use Meta::*;
|
||||||
|
use NestedMeta::*;
|
||||||
|
|
||||||
|
let iter = attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|attr| {
|
||||||
|
let path = &attr.path;
|
||||||
|
match quote!(#path).to_string() == "structopt" {
|
||||||
|
true => Some(
|
||||||
|
attr.interpret_meta()
|
||||||
|
.expect(&format!("invalid structopt syntax: {}", quote!(attr))),
|
||||||
|
),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flat_map(|m| match m {
|
||||||
|
List(l) => l.nested,
|
||||||
|
tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
|
||||||
|
})
|
||||||
|
.map(|m| match m {
|
||||||
|
Meta(m) => m,
|
||||||
|
ref tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
|
||||||
|
});
|
||||||
|
for attr in iter {
|
||||||
|
match attr {
|
||||||
|
NameValue(MetaNameValue {
|
||||||
|
ident,
|
||||||
|
lit: Str(value),
|
||||||
|
..
|
||||||
|
}) => self.push_str_method(&ident.to_string(), &value.value()),
|
||||||
|
NameValue(MetaNameValue { ident, lit, .. }) => self.methods.push(Method {
|
||||||
|
name: ident.to_string(),
|
||||||
|
args: quote!(#lit),
|
||||||
|
}),
|
||||||
|
List(MetaList {
|
||||||
|
ref ident,
|
||||||
|
ref nested,
|
||||||
|
..
|
||||||
|
}) if ident == "parse" =>
|
||||||
|
{
|
||||||
|
if nested.len() != 1 {
|
||||||
|
panic!("parse must have exactly one argument");
|
||||||
|
}
|
||||||
|
self.has_custom_parser = true;
|
||||||
|
self.parser = match nested[0] {
|
||||||
|
Meta(NameValue(MetaNameValue {
|
||||||
|
ref ident,
|
||||||
|
lit: Str(ref v),
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
let function: syn::Path = v.parse().expect("parser function path");
|
||||||
|
let parser = ident.to_string().parse().unwrap();
|
||||||
|
(parser, quote!(#function))
|
||||||
|
}
|
||||||
|
Meta(Word(ref i)) => {
|
||||||
|
use Parser::*;
|
||||||
|
let parser = i.to_string().parse().unwrap();
|
||||||
|
let function = match parser {
|
||||||
|
FromStr => quote!(::std::convert::From::from),
|
||||||
|
TryFromStr => quote!(::std::str::FromStr::from_str),
|
||||||
|
FromOsStr => quote!(::std::convert::From::from),
|
||||||
|
TryFromOsStr => panic!(
|
||||||
|
"cannot omit parser function name with `try_from_os_str`"
|
||||||
|
),
|
||||||
|
FromOccurrences => quote!({ |v| v as _ }),
|
||||||
|
};
|
||||||
|
(parser, function)
|
||||||
|
}
|
||||||
|
ref l @ _ => panic!("unknown value parser specification: {}", quote!(#l)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
List(MetaList {
|
||||||
|
ref ident,
|
||||||
|
ref nested,
|
||||||
|
..
|
||||||
|
}) if ident == "raw" =>
|
||||||
|
{
|
||||||
|
for method in nested {
|
||||||
|
match *method {
|
||||||
|
Meta(NameValue(MetaNameValue {
|
||||||
|
ref ident,
|
||||||
|
lit: Str(ref v),
|
||||||
|
..
|
||||||
|
})) => self.push_raw_method(&ident.to_string(), v),
|
||||||
|
ref mi @ _ => panic!("unsupported raw entry: {}", quote!(#mi)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Word(ref w) if w == "subcommand" => {
|
||||||
|
self.set_kind(Kind::Subcommand(Ty::Other));
|
||||||
|
}
|
||||||
|
Word(ref w) if w == "flatten" => {
|
||||||
|
self.set_kind(Kind::FlattenStruct);
|
||||||
|
}
|
||||||
|
ref i @ List(..) | ref i @ Word(..) => panic!("unsupported option: {}", quote!(#i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn push_raw_method(&mut self, name: &str, args: &LitStr) {
|
||||||
|
let ts: TokenStream = args.value().parse().expect(&format!(
|
||||||
|
"bad parameter {} = {}: the parameter must be valid rust code",
|
||||||
|
name,
|
||||||
|
quote!(#args)
|
||||||
|
));
|
||||||
|
self.methods.push(Method {
|
||||||
|
name: name.to_string(),
|
||||||
|
args: quote!(#(#ts)*),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
|
||||||
|
let doc_comments: Vec<_> = attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|attr| {
|
||||||
|
let path = &attr.path;
|
||||||
|
match quote!(#path).to_string() == "doc" {
|
||||||
|
true => attr.interpret_meta(),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter_map(|attr| {
|
||||||
|
use Lit::*;
|
||||||
|
use Meta::*;
|
||||||
|
if let NameValue(MetaNameValue {
|
||||||
|
ident, lit: Str(s), ..
|
||||||
|
}) = attr
|
||||||
|
{
|
||||||
|
if ident != "doc" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let value = s.value();
|
||||||
|
let text = value
|
||||||
|
.trim_left_matches("//!")
|
||||||
|
.trim_left_matches("///")
|
||||||
|
.trim_left_matches("/*!")
|
||||||
|
.trim_left_matches("/**")
|
||||||
|
.trim_right_matches("*/")
|
||||||
|
.trim();
|
||||||
|
if text.is_empty() {
|
||||||
|
Some("\n\n".to_string())
|
||||||
|
} else {
|
||||||
|
Some(text.to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if doc_comments.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let arg = doc_comments
|
||||||
|
.join(" ")
|
||||||
|
.split('\n')
|
||||||
|
.map(|l| l.trim().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
self.methods.push(Method {
|
||||||
|
name: name.to_string(),
|
||||||
|
args: quote!(#arg),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pub fn from_struct(attrs: &[Attribute], name: String) -> Attrs {
|
||||||
|
let mut res = Self::new(name);
|
||||||
|
let attrs_with_env = [
|
||||||
|
("version", "CARGO_PKG_VERSION"),
|
||||||
|
("about", "CARGO_PKG_DESCRIPTION"),
|
||||||
|
("author", "CARGO_PKG_AUTHORS"),
|
||||||
|
];
|
||||||
|
attrs_with_env
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&(m, v)| env::var(v).ok().and_then(|arg| Some((m, arg))))
|
||||||
|
.filter(|&(_, ref arg)| !arg.is_empty())
|
||||||
|
.for_each(|(name, arg)| {
|
||||||
|
let new_arg = if name == "author" {
|
||||||
|
arg.replace(":", ", ")
|
||||||
|
} else {
|
||||||
|
arg
|
||||||
|
};
|
||||||
|
res.push_str_method(name, &new_arg);
|
||||||
|
});
|
||||||
|
res.push_doc_comment(attrs, "about");
|
||||||
|
res.push_attrs(attrs);
|
||||||
|
if res.has_custom_parser {
|
||||||
|
panic!("parse attribute is only allowed on fields");
|
||||||
|
}
|
||||||
|
match res.kind {
|
||||||
|
Kind::Subcommand(_) => panic!("subcommand is only allowed on fields"),
|
||||||
|
Kind::FlattenStruct => panic!("flatten is only allowed on fields"),
|
||||||
|
Kind::Arg(_) => res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn ty_from_field(ty: &syn::Type) -> Ty {
|
||||||
|
if let Path(TypePath {
|
||||||
|
path: syn::Path { ref segments, .. },
|
||||||
|
..
|
||||||
|
}) = *ty
|
||||||
|
{
|
||||||
|
match segments.iter().last().unwrap().ident.to_string().as_str() {
|
||||||
|
"bool" => Ty::Bool,
|
||||||
|
"Option" => Ty::Option,
|
||||||
|
"Vec" => Ty::Vec,
|
||||||
|
_ => Ty::Other,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ty::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_field(field: &syn::Field) -> Attrs {
|
||||||
|
let name = field.ident.as_ref().unwrap().to_string();
|
||||||
|
let mut res = Self::new(name);
|
||||||
|
res.push_doc_comment(&field.attrs, "help");
|
||||||
|
res.push_attrs(&field.attrs);
|
||||||
|
|
||||||
|
match res.kind {
|
||||||
|
Kind::FlattenStruct => {
|
||||||
|
if res.has_custom_parser {
|
||||||
|
panic!("parse attribute is not allowed for flattened entry");
|
||||||
|
}
|
||||||
|
if !res.methods.is_empty() {
|
||||||
|
panic!("methods and doc comments are not allowed for flattened entry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kind::Subcommand(_) => {
|
||||||
|
if res.has_custom_parser {
|
||||||
|
panic!("parse attribute is not allowed for subcommand");
|
||||||
|
}
|
||||||
|
if !res.methods.iter().all(|m| m.name == "help") {
|
||||||
|
panic!("methods in attributes is not allowed for subcommand");
|
||||||
|
}
|
||||||
|
res.kind = Kind::Subcommand(Self::ty_from_field(&field.ty));
|
||||||
|
}
|
||||||
|
Kind::Arg(_) => {
|
||||||
|
let mut ty = Self::ty_from_field(&field.ty);
|
||||||
|
if res.has_custom_parser {
|
||||||
|
match ty {
|
||||||
|
Ty::Option | Ty::Vec => (),
|
||||||
|
_ => ty = Ty::Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match ty {
|
||||||
|
Ty::Bool => {
|
||||||
|
if res.has_method("default_value") {
|
||||||
|
panic!("default_value is meaningless for bool")
|
||||||
|
}
|
||||||
|
if res.has_method("required") {
|
||||||
|
panic!("required is meaningless for bool")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ty::Option => {
|
||||||
|
if res.has_method("default_value") {
|
||||||
|
panic!("default_value is meaningless for Option")
|
||||||
|
}
|
||||||
|
if res.has_method("required") {
|
||||||
|
panic!("required is meaningless for Option")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
res.kind = Kind::Arg(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
fn set_kind(&mut self, kind: Kind) {
|
||||||
|
if let Kind::Arg(_) = self.kind {
|
||||||
|
self.kind = kind;
|
||||||
|
} else {
|
||||||
|
panic!("subcommands cannot be flattened");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn has_method(&self, method: &str) -> bool {
|
||||||
|
self.methods.iter().find(|m| m.name == method).is_some()
|
||||||
|
}
|
||||||
|
pub fn methods(&self) -> TokenStream {
|
||||||
|
let methods = self.methods.iter().map(|&Method { ref name, ref args }| {
|
||||||
|
let name = Ident::new(&name, Span::call_site());
|
||||||
|
quote!( .#name(#args) )
|
||||||
|
});
|
||||||
|
quote!( #(#methods)* )
|
||||||
|
}
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
pub fn parser(&self) -> &(Parser, TokenStream) {
|
||||||
|
&self.parser
|
||||||
|
}
|
||||||
|
pub fn kind(&self) -> Kind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
}
|
443
structopt-derive/src/lib.rs
Normal file
443
structopt-derive/src/lib.rs
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! This crate is custom derive for StructOpt. It should not be used
|
||||||
|
//! directly. See [structopt documentation](https://docs.rs/structopt)
|
||||||
|
//! for the usage of `#[derive(StructOpt)]`.
|
||||||
|
|
||||||
|
extern crate proc_macro;
|
||||||
|
extern crate syn;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate quote;
|
||||||
|
extern crate proc_macro2;
|
||||||
|
|
||||||
|
mod attrs;
|
||||||
|
|
||||||
|
use attrs::{Attrs, Kind, Parser, Ty};
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::token::Comma;
|
||||||
|
use syn::*;
|
||||||
|
|
||||||
|
/// Generates the `StructOpt` impl.
|
||||||
|
#[proc_macro_derive(StructOpt, attributes(structopt))]
|
||||||
|
pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input: DeriveInput = syn::parse(input).unwrap();
|
||||||
|
let gen = impl_structopt(&input);
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub_type(t: &syn::Type) -> Option<&syn::Type> {
|
||||||
|
let segs = match *t {
|
||||||
|
syn::Type::Path(TypePath {
|
||||||
|
path: syn::Path { ref segments, .. },
|
||||||
|
..
|
||||||
|
}) => segments,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
match *segs.iter().last().unwrap() {
|
||||||
|
PathSegment {
|
||||||
|
arguments:
|
||||||
|
PathArguments::AngleBracketed(AngleBracketedGenericArguments { ref args, .. }),
|
||||||
|
..
|
||||||
|
} if args.len() == 1 =>
|
||||||
|
{
|
||||||
|
if let GenericArgument::Type(ref ty) = args[0] {
|
||||||
|
Some(ty)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a block of code to add arguments/subcommands corresponding to
|
||||||
|
/// the `fields` to an app.
|
||||||
|
fn gen_augmentation(fields: &Punctuated<Field, Comma>, app_var: &Ident) -> TokenStream {
|
||||||
|
let subcmds: Vec<_> = fields
|
||||||
|
.iter()
|
||||||
|
.filter_map(|field| {
|
||||||
|
let attrs = Attrs::from_field(&field);
|
||||||
|
if let Kind::Subcommand(ty) = attrs.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! {
|
||||||
|
let #app_var = #app_var.setting(
|
||||||
|
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(quote!{
|
||||||
|
let #app_var = <#subcmd_type>::augment_clap( #app_var );
|
||||||
|
#required
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
subcmds.len() <= 1,
|
||||||
|
"cannot have more than one nested subcommand"
|
||||||
|
);
|
||||||
|
|
||||||
|
let args = fields.iter().filter_map(|field| {
|
||||||
|
let attrs = Attrs::from_field(field);
|
||||||
|
match attrs.kind() {
|
||||||
|
Kind::Subcommand(_) => None,
|
||||||
|
Kind::FlattenStruct => {
|
||||||
|
let ty = &field.ty;
|
||||||
|
Some(quote! {
|
||||||
|
let #app_var = <#ty>::augment_clap(#app_var);
|
||||||
|
let #app_var = if <#ty>::is_subcommand() {
|
||||||
|
#app_var.setting(::structopt::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),
|
||||||
|
_ => &field.ty,
|
||||||
|
};
|
||||||
|
|
||||||
|
let occurences = attrs.parser().0 == Parser::FromOccurrences;
|
||||||
|
|
||||||
|
let validator = match *attrs.parser() {
|
||||||
|
(Parser::TryFromStr, ref f) => quote! {
|
||||||
|
.validator(|s| {
|
||||||
|
#f(&s)
|
||||||
|
.map(|_: #convert_type| ())
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
(Parser::TryFromOsStr, ref f) => quote! {
|
||||||
|
.validator_os(|s| #f(&s).map(|_: #convert_type| ()))
|
||||||
|
},
|
||||||
|
_ => quote!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let modifier = match ty {
|
||||||
|
Ty::Bool => quote!( .takes_value(false).multiple(false) ),
|
||||||
|
Ty::Option => quote!( .takes_value(true).multiple(false) #validator ),
|
||||||
|
Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ),
|
||||||
|
Ty::Other if occurences => quote!( .takes_value(false).multiple(true) ),
|
||||||
|
Ty::Other => {
|
||||||
|
let required = !attrs.has_method("default_value");
|
||||||
|
quote!( .takes_value(true).multiple(false).required(#required) #validator )
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let methods = attrs.methods();
|
||||||
|
let name = attrs.name();
|
||||||
|
Some(quote!{
|
||||||
|
let #app_var = #app_var.arg(
|
||||||
|
::structopt::clap::Arg::with_name(#name)
|
||||||
|
#modifier
|
||||||
|
#methods
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {{
|
||||||
|
#( #args )*
|
||||||
|
#( #subcmds )*
|
||||||
|
#app_var
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_constructor(fields: &Punctuated<Field, Comma>) -> TokenStream {
|
||||||
|
let fields = fields.iter().map(|field| {
|
||||||
|
let attrs = Attrs::from_field(field);
|
||||||
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
|
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!( .unwrap() ),
|
||||||
|
};
|
||||||
|
quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper)
|
||||||
|
}
|
||||||
|
Kind::FlattenStruct => quote!(#field_name: ::structopt::StructOpt::from_clap(matches)),
|
||||||
|
Kind::Arg(ty) => {
|
||||||
|
use Parser::*;
|
||||||
|
let (value_of, values_of, parse) = match *attrs.parser() {
|
||||||
|
(FromStr, ref f) => (quote!(value_of), quote!(values_of), f.clone()),
|
||||||
|
(TryFromStr, ref f) => (
|
||||||
|
quote!(value_of),
|
||||||
|
quote!(values_of),
|
||||||
|
quote!(|s| #f(s).unwrap()),
|
||||||
|
),
|
||||||
|
(FromOsStr, ref f) => (quote!(value_of_os), quote!(values_of_os), f.clone()),
|
||||||
|
(TryFromOsStr, ref f) => (
|
||||||
|
quote!(value_of_os),
|
||||||
|
quote!(values_of_os),
|
||||||
|
quote!(|s| #f(s).unwrap()),
|
||||||
|
),
|
||||||
|
(FromOccurrences, ref f) => (quote!(occurrences_of), quote!(), f.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let occurences = attrs.parser().0 == Parser::FromOccurrences;
|
||||||
|
let name = attrs.name();
|
||||||
|
let field_value = match ty {
|
||||||
|
Ty::Bool => quote!(matches.is_present(#name)),
|
||||||
|
Ty::Option => quote! {
|
||||||
|
matches.#value_of(#name)
|
||||||
|
.as_ref()
|
||||||
|
.map(#parse)
|
||||||
|
},
|
||||||
|
Ty::Vec => quote! {
|
||||||
|
matches.#values_of(#name)
|
||||||
|
.map(|v| v.map(#parse).collect())
|
||||||
|
.unwrap_or_else(Vec::new)
|
||||||
|
},
|
||||||
|
Ty::Other if occurences => quote! {
|
||||||
|
#parse(matches.#value_of(#name))
|
||||||
|
},
|
||||||
|
Ty::Other => quote! {
|
||||||
|
matches.#value_of(#name)
|
||||||
|
.map(#parse)
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
quote!( #field_name: #field_value )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {{
|
||||||
|
#( #fields ),*
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_from_clap(struct_name: &Ident, fields: &Punctuated<Field, Comma>) -> TokenStream {
|
||||||
|
let field_block = gen_constructor(fields);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
|
||||||
|
#struct_name #field_block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_clap(attrs: &[Attribute]) -> TokenStream {
|
||||||
|
let name = std::env::var("CARGO_PKG_NAME")
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_else(String::default);
|
||||||
|
let attrs = Attrs::from_struct(attrs, name);
|
||||||
|
let name = attrs.name();
|
||||||
|
let methods = attrs.methods();
|
||||||
|
quote!(::structopt::clap::App::new(#name)#methods)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_clap_struct(struct_attrs: &[Attribute]) -> TokenStream {
|
||||||
|
let gen = gen_clap(struct_attrs);
|
||||||
|
quote! {
|
||||||
|
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
|
||||||
|
let app = #gen;
|
||||||
|
Self::augment_clap(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_augment_clap(fields: &Punctuated<Field, Comma>) -> TokenStream {
|
||||||
|
let app_var = Ident::new("app", Span::call_site());
|
||||||
|
let augmentation = gen_augmentation(fields, &app_var);
|
||||||
|
quote! {
|
||||||
|
pub fn augment_clap<'a, 'b>(
|
||||||
|
#app_var: ::structopt::clap::App<'a, 'b>
|
||||||
|
) -> ::structopt::clap::App<'a, 'b> {
|
||||||
|
#augmentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_clap_enum(enum_attrs: &[Attribute]) -> TokenStream {
|
||||||
|
let gen = gen_clap(enum_attrs);
|
||||||
|
quote! {
|
||||||
|
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
|
||||||
|
let app = #gen
|
||||||
|
.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp);
|
||||||
|
Self::augment_clap(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_augment_clap_enum(variants: &Punctuated<Variant, Comma>) -> TokenStream {
|
||||||
|
use syn::Fields::*;
|
||||||
|
|
||||||
|
let subcommands = variants.iter().map(|variant| {
|
||||||
|
let name = variant.ident.to_string();
|
||||||
|
let attrs = Attrs::from_struct(&variant.attrs, name);
|
||||||
|
let app_var = Ident::new("subcommand", Span::call_site());
|
||||||
|
let arg_block = match variant.fields {
|
||||||
|
Named(ref fields) => gen_augmentation(&fields.named, &app_var),
|
||||||
|
Unit => quote!( #app_var ),
|
||||||
|
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
|
||||||
|
let ty = &unnamed[0];
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let #app_var = <#ty>::augment_clap(#app_var);
|
||||||
|
if <#ty>::is_subcommand() {
|
||||||
|
#app_var.setting(
|
||||||
|
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
#app_var
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = attrs.name();
|
||||||
|
let from_attrs = attrs.methods();
|
||||||
|
quote! {
|
||||||
|
.subcommand({
|
||||||
|
let #app_var = ::structopt::clap::SubCommand::with_name(#name);
|
||||||
|
let #app_var = #arg_block;
|
||||||
|
#app_var#from_attrs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub fn augment_clap<'a, 'b>(
|
||||||
|
app: ::structopt::clap::App<'a, 'b>
|
||||||
|
) -> ::structopt::clap::App<'a, 'b> {
|
||||||
|
app #( #subcommands )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_from_clap_enum(name: &Ident) -> TokenStream {
|
||||||
|
quote! {
|
||||||
|
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
|
||||||
|
<#name>::from_subcommand(matches.subcommand())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> TokenStream {
|
||||||
|
use syn::Fields::*;
|
||||||
|
|
||||||
|
let match_arms = variants.iter().map(|variant| {
|
||||||
|
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.to_string());
|
||||||
|
let sub_name = attrs.name();
|
||||||
|
let variant_name = &variant.ident;
|
||||||
|
let constructor_block = match variant.fields {
|
||||||
|
Named(ref fields) => gen_constructor(&fields.named),
|
||||||
|
Unit => quote!(),
|
||||||
|
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
||||||
|
let ty = &fields.unnamed[0];
|
||||||
|
quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) )
|
||||||
|
}
|
||||||
|
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
(#sub_name, Some(matches)) =>
|
||||||
|
Some(#name :: #variant_name #constructor_block)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub fn from_subcommand<'a, 'b>(
|
||||||
|
sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>)
|
||||||
|
) -> Option<Self> {
|
||||||
|
match sub {
|
||||||
|
#( #match_arms ),*,
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_structopt_for_struct(
|
||||||
|
name: &Ident,
|
||||||
|
fields: &Punctuated<Field, Comma>,
|
||||||
|
attrs: &[Attribute],
|
||||||
|
) -> TokenStream {
|
||||||
|
let clap = gen_clap_struct(attrs);
|
||||||
|
let augment_clap = gen_augment_clap(fields);
|
||||||
|
let from_clap = gen_from_clap(name, fields);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
impl ::structopt::StructOpt for #name {
|
||||||
|
#clap
|
||||||
|
#from_clap
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code, unreachable_code)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl #name {
|
||||||
|
#augment_clap
|
||||||
|
pub fn is_subcommand() -> bool { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_structopt_for_enum(
|
||||||
|
name: &Ident,
|
||||||
|
variants: &Punctuated<Variant, Comma>,
|
||||||
|
attrs: &[Attribute],
|
||||||
|
) -> TokenStream {
|
||||||
|
let clap = gen_clap_enum(attrs);
|
||||||
|
let augment_clap = gen_augment_clap_enum(variants);
|
||||||
|
let from_clap = gen_from_clap_enum(name);
|
||||||
|
let from_subcommand = gen_from_subcommand(name, variants);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl ::structopt::StructOpt for #name {
|
||||||
|
#clap
|
||||||
|
#from_clap
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables, dead_code, unreachable_code)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl #name {
|
||||||
|
#augment_clap
|
||||||
|
#from_subcommand
|
||||||
|
pub fn is_subcommand() -> bool { true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_structopt(input: &DeriveInput) -> TokenStream {
|
||||||
|
use syn::Data::*;
|
||||||
|
|
||||||
|
let struct_name = &input.ident;
|
||||||
|
let inner_impl = match input.data {
|
||||||
|
Struct(DataStruct {
|
||||||
|
fields: syn::Fields::Named(ref fields),
|
||||||
|
..
|
||||||
|
}) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs),
|
||||||
|
Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs),
|
||||||
|
_ => panic!("structopt only supports non-tuple structs and enums"),
|
||||||
|
};
|
||||||
|
|
||||||
|
quote!(#inner_impl)
|
||||||
|
}
|
111
tests/arguments.rs
Normal file
111
tests/arguments.rs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::clap;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_argument() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test", "42"]));
|
||||||
|
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "42", "24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_argument() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
arg: Option<i32>,
|
||||||
|
}
|
||||||
|
assert_eq!(Opt { arg: Some(42) }, Opt::from_iter(&["test", "42"]));
|
||||||
|
assert_eq!(Opt { arg: None }, Opt::from_iter(&["test"]));
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "42", "24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn argument_with_default() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(default_value = "42")]
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
assert_eq!(Opt { arg: 24 }, Opt::from_iter(&["test", "24"]));
|
||||||
|
assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test"]));
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "42", "24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn argument_with_raw_default() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(raw(default_value = r#""42""#))]
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
assert_eq!(Opt { arg: 24 }, Opt::from_iter(&["test", "24"]));
|
||||||
|
assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test"]));
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "42", "24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arguments() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
arg: Vec<i32>,
|
||||||
|
}
|
||||||
|
assert_eq!(Opt { arg: vec![24] }, Opt::from_iter(&["test", "24"]));
|
||||||
|
assert_eq!(Opt { arg: vec![] }, Opt::from_iter(&["test"]));
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: vec![24, 42] },
|
||||||
|
Opt::from_iter(&["test", "24", "42"])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arguments_safe() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
arg: Vec<i32>,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: vec![24] },
|
||||||
|
Opt::from_iter_safe(&["test", "24"]).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(Opt { arg: vec![] }, Opt::from_iter_safe(&["test"]).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: vec![24, 42] },
|
||||||
|
Opt::from_iter_safe(&["test", "24", "42"]).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
clap::ErrorKind::ValueValidation,
|
||||||
|
Opt::from_iter_safe(&["test", "NOPE"]).err().unwrap().kind
|
||||||
|
);
|
||||||
|
}
|
39
tests/author_version_about.rs
Normal file
39
tests/author_version_about.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_author_version_about() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
#[structopt(name = "foo", about = "", author = "", version = "")]
|
||||||
|
struct Opt {}
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
Opt::clap().write_long_help(&mut output).unwrap();
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
|
||||||
|
assert!(output.starts_with("foo \n\nUSAGE:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_env() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
#[structopt()]
|
||||||
|
struct Opt {}
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
Opt::clap().write_long_help(&mut output).unwrap();
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
assert!(output.starts_with("structopt 0.2."));
|
||||||
|
assert!(output.contains("Guillaume Pinot <texitoi@texitoi.eu>, others"));
|
||||||
|
assert!(output.contains("Parse command line argument by defining a struct."));
|
||||||
|
}
|
290
tests/custom-string-parsers.rs
Normal file
290
tests/custom-string-parsers.rs
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct PathOpt {
|
||||||
|
#[structopt(short = "p", long = "path", parse(from_os_str))]
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
#[structopt(short = "d", default_value = "../", parse(from_os_str))]
|
||||||
|
default_path: PathBuf,
|
||||||
|
|
||||||
|
#[structopt(short = "v", parse(from_os_str))]
|
||||||
|
vector_path: Vec<PathBuf>,
|
||||||
|
|
||||||
|
#[structopt(short = "o", parse(from_os_str))]
|
||||||
|
option_path_1: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[structopt(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::from_clap(&PathOpt::clap().get_matches_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(StructOpt, PartialEq, Debug)]
|
||||||
|
struct HexOpt {
|
||||||
|
#[structopt(short = "n", parse(try_from_str = "parse_hex"))]
|
||||||
|
number: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_hex() {
|
||||||
|
assert_eq!(
|
||||||
|
HexOpt { number: 5 },
|
||||||
|
HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "5"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
HexOpt { number: 0xabcdef },
|
||||||
|
HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"]))
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = HexOpt::clap()
|
||||||
|
.get_matches_from_safe(&["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, OsString> {
|
||||||
|
Ok("D")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct NoOpOpt {
|
||||||
|
#[structopt(short = "a", parse(from_str = "custom_parser_1"))]
|
||||||
|
a: &'static str,
|
||||||
|
#[structopt(short = "b", parse(try_from_str = "custom_parser_2"))]
|
||||||
|
b: &'static str,
|
||||||
|
#[structopt(short = "c", parse(from_os_str = "custom_parser_3"))]
|
||||||
|
c: &'static str,
|
||||||
|
#[structopt(short = "d", 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::from_clap(&NoOpOpt::clap().get_matches_from(&["test", "-a=?", "-b=?", "-c=?", "-d=?"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: can't use `Vec<u8>` directly, as structopt would instead look for
|
||||||
|
// conversion function from `&str` to `u8`.
|
||||||
|
type Bytes = Vec<u8>;
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct DefaultedOpt {
|
||||||
|
#[structopt(short = "b", parse(from_str))]
|
||||||
|
bytes: Bytes,
|
||||||
|
|
||||||
|
#[structopt(short = "i", parse(try_from_str))]
|
||||||
|
integer: u64,
|
||||||
|
|
||||||
|
#[structopt(short = "p", 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::from_clap(&DefaultedOpt::clap().get_matches_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(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Occurrences {
|
||||||
|
#[structopt(short = "s", long = "signed", parse(from_occurrences))]
|
||||||
|
signed: i32,
|
||||||
|
|
||||||
|
#[structopt(short = "l", parse(from_occurrences))]
|
||||||
|
little_signed: i8,
|
||||||
|
|
||||||
|
#[structopt(short = "u", parse(from_occurrences))]
|
||||||
|
unsigned: usize,
|
||||||
|
|
||||||
|
#[structopt(short = "r", parse(from_occurrences))]
|
||||||
|
little_unsigned: u8,
|
||||||
|
|
||||||
|
#[structopt(short = "c", long = "custom", 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::from_clap(&Occurrences::clap().get_matches_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(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "d", parse(try_from_str = "parse_bool"))]
|
||||||
|
debug: bool,
|
||||||
|
#[structopt(short = "v", default_value = "false", parse(try_from_str = "parse_bool"))]
|
||||||
|
verbose: bool,
|
||||||
|
#[structopt(short = "t", parse(try_from_str = "parse_bool"))]
|
||||||
|
tribool: Option<bool>,
|
||||||
|
#[structopt(short = "b", parse(try_from_str = "parse_bool"))]
|
||||||
|
bitset: Vec<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
|
||||||
|
assert!(Opt::clap().get_matches_from_safe(&["test", "-d"]).is_err());
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-dfoo"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
debug: false,
|
||||||
|
verbose: false,
|
||||||
|
tribool: None,
|
||||||
|
bitset: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "-dfalse"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
debug: true,
|
||||||
|
verbose: false,
|
||||||
|
tribool: None,
|
||||||
|
bitset: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "-dtrue"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
debug: true,
|
||||||
|
verbose: false,
|
||||||
|
tribool: None,
|
||||||
|
bitset: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "-dtrue", "-vfalse"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
debug: true,
|
||||||
|
verbose: true,
|
||||||
|
tribool: None,
|
||||||
|
bitset: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "-dtrue", "-vtrue"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
debug: true,
|
||||||
|
verbose: false,
|
||||||
|
tribool: Some(false),
|
||||||
|
bitset: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "-dtrue", "-tfalse"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
debug: true,
|
||||||
|
verbose: false,
|
||||||
|
tribool: Some(true),
|
||||||
|
bitset: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "-dtrue", "-ttrue"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
debug: true,
|
||||||
|
verbose: false,
|
||||||
|
tribool: None,
|
||||||
|
bitset: vec![false, true, false, false],
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"])
|
||||||
|
);
|
||||||
|
}
|
52
tests/deny-warnings.rs
Normal file
52
tests/deny-warnings.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#![deny(warnings)]
|
||||||
|
#![cfg(feature = "nightly")] // TODO: remove that when never is stable
|
||||||
|
#![feature(never_type)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
fn try_str(s: &str) -> Result<String, !> {
|
||||||
|
Ok(s.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn warning_never_struct() {
|
||||||
|
#[derive(Debug, PartialEq, StructOpt)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(parse(try_from_str = "try_str"))]
|
||||||
|
s: String,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
s: "foo".to_string()
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "foo"])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn warning_never_enum() {
|
||||||
|
#[derive(Debug, PartialEq, StructOpt)]
|
||||||
|
enum Opt {
|
||||||
|
Foo {
|
||||||
|
#[structopt(parse(try_from_str = "try_str"))]
|
||||||
|
s: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt::Foo {
|
||||||
|
s: "foo".to_string()
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "Foo", "foo"])
|
||||||
|
);
|
||||||
|
}
|
68
tests/doc-comments-help.rs
Normal file
68
tests/doc-comments-help.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commets_intead_of_actual_help() {
|
||||||
|
/// Lorem ipsum
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct LoremIpsum {
|
||||||
|
/// Fooify a bar
|
||||||
|
/// and a baz
|
||||||
|
#[structopt(short = "f", long = "foo")]
|
||||||
|
foo: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
LoremIpsum::clap().write_long_help(&mut output).unwrap();
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
|
||||||
|
assert!(output.contains("Lorem ipsum"));
|
||||||
|
assert!(output.contains("Fooify a bar and a baz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_is_better_than_comments() {
|
||||||
|
/// Lorem ipsum
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
#[structopt(name = "lorem-ipsum", about = "Dolor sit amet")]
|
||||||
|
struct LoremIpsum {
|
||||||
|
/// Fooify a bar
|
||||||
|
#[structopt(short = "f", long = "foo", help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")]
|
||||||
|
foo: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
LoremIpsum::clap().write_long_help(&mut output).unwrap();
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
|
||||||
|
assert!(output.contains("Dolor sit amet"));
|
||||||
|
assert!(!output.contains("Lorem ipsum"));
|
||||||
|
assert!(output.contains("DO NOT PASS A BAR"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_line_in_doc_comment_is_double_linefeed() {
|
||||||
|
/// Foo.
|
||||||
|
///
|
||||||
|
/// Bar
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
#[structopt(name = "lorem-ipsum", author = "", version = "")]
|
||||||
|
struct LoremIpsum {}
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
LoremIpsum::clap().write_long_help(&mut output).unwrap();
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
|
||||||
|
println!("{}", output);
|
||||||
|
assert!(output.starts_with("lorem-ipsum \nFoo.\n\nBar\n\nUSAGE:"));
|
||||||
|
}
|
142
tests/flags.rs
Normal file
142
tests/flags.rs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unique_flag() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", long = "alice")]
|
||||||
|
alice: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: false },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: true },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: true },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice"]))
|
||||||
|
);
|
||||||
|
assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err());
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a", "foo"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a", "-a"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a", "--alice"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_flag() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", long = "alice", parse(from_occurrences))]
|
||||||
|
alice: u64,
|
||||||
|
#[structopt(short = "b", long = "bob", parse(from_occurrences))]
|
||||||
|
bob: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: 0, bob: 0 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: 1, bob: 0 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: 2, bob: 0 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-a"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: 2, bob: 2 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--alice", "-bb"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { alice: 3, bob: 1 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-aaa", "--bob"]))
|
||||||
|
);
|
||||||
|
assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err());
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a", "foo"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn combined_flags() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", long = "alice")]
|
||||||
|
alice: bool,
|
||||||
|
#[structopt(short = "b", long = "bob", parse(from_occurrences))]
|
||||||
|
bob: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
alice: false,
|
||||||
|
bob: 0
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
alice: true,
|
||||||
|
bob: 0
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
alice: true,
|
||||||
|
bob: 0
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
alice: false,
|
||||||
|
bob: 1
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
alice: true,
|
||||||
|
bob: 1
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice", "--bob"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
alice: true,
|
||||||
|
bob: 4
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-bb", "-a", "-bb"]))
|
||||||
|
);
|
||||||
|
}
|
102
tests/flatten.rs
Normal file
102
tests/flatten.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flatten() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Common {
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
common: Common,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
common: Common { arg: 42 }
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "42"])
|
||||||
|
);
|
||||||
|
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "42", "24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn flatten_twice() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Common {
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
c1: Common,
|
||||||
|
// Defines "arg" twice, so this should not work.
|
||||||
|
#[structopt(flatten)]
|
||||||
|
c2: Common,
|
||||||
|
}
|
||||||
|
Opt::from_iter(&["test", "42", "43"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flatten_in_subcommand() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Common {
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Add {
|
||||||
|
#[structopt(short = "i")]
|
||||||
|
interactive: bool,
|
||||||
|
#[structopt(flatten)]
|
||||||
|
common: Common,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Opt {
|
||||||
|
#[structopt(name = "fetch")]
|
||||||
|
Fetch {
|
||||||
|
#[structopt(short = "a")]
|
||||||
|
all: bool,
|
||||||
|
#[structopt(flatten)]
|
||||||
|
common: Common,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[structopt(name = "add")]
|
||||||
|
Add(Add),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Opt::Fetch {
|
||||||
|
all: false,
|
||||||
|
common: Common { arg: 42 }
|
||||||
|
},
|
||||||
|
Opt::from_iter(&["test", "fetch", "42"])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt::Add(Add {
|
||||||
|
interactive: true,
|
||||||
|
common: Common { arg: 43 }
|
||||||
|
}),
|
||||||
|
Opt::from_iter(&["test", "add", "-i", "43"])
|
||||||
|
);
|
||||||
|
}
|
206
tests/nested-subcommands.rs
Normal file
206
tests/nested-subcommands.rs
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "f", long = "force")]
|
||||||
|
force: bool,
|
||||||
|
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||||
|
verbose: u64,
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
cmd: Sub,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Sub {
|
||||||
|
#[structopt(name = "fetch")]
|
||||||
|
Fetch {},
|
||||||
|
#[structopt(name = "add")]
|
||||||
|
Add {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt2 {
|
||||||
|
#[structopt(short = "f", long = "force")]
|
||||||
|
force: bool,
|
||||||
|
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||||
|
verbose: u64,
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
cmd: Option<Sub>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_cmd() {
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Opt2 {
|
||||||
|
force: false,
|
||||||
|
verbose: 0,
|
||||||
|
cmd: None
|
||||||
|
},
|
||||||
|
Opt2::from_clap(&Opt2::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fetch() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
force: false,
|
||||||
|
verbose: 3,
|
||||||
|
cmd: Sub::Fetch {}
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vvv", "fetch"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
force: true,
|
||||||
|
verbose: 0,
|
||||||
|
cmd: Sub::Fetch {}
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--force", "fetch"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
force: false,
|
||||||
|
verbose: 0,
|
||||||
|
cmd: Sub::Add {}
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
force: false,
|
||||||
|
verbose: 2,
|
||||||
|
cmd: Sub::Add {}
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vv", "add"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_badinput() {
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "badcmd"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "add", "--verbose"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "--badopt", "add"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badopt"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt3 {
|
||||||
|
#[structopt(short = "a", long = "all")]
|
||||||
|
all: bool,
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
cmd: Sub2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Sub2 {
|
||||||
|
#[structopt(name = "foo")]
|
||||||
|
Foo {
|
||||||
|
file: String,
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
cmd: Sub3,
|
||||||
|
},
|
||||||
|
#[structopt(name = "bar")]
|
||||||
|
Bar {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Sub3 {
|
||||||
|
#[structopt(name = "baz")]
|
||||||
|
Baz {},
|
||||||
|
#[structopt(name = "quux")]
|
||||||
|
Quux {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_subsubcommand() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt3 {
|
||||||
|
all: true,
|
||||||
|
cmd: Sub2::Foo {
|
||||||
|
file: "lib.rs".to_string(),
|
||||||
|
cmd: Sub3::Quux {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "--all", "foo", "lib.rs", "quux"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum SubSubCmdWithOption {
|
||||||
|
#[structopt(name = "remote")]
|
||||||
|
Remote {
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
cmd: Option<Remote>,
|
||||||
|
},
|
||||||
|
#[structopt(name = "stash")]
|
||||||
|
Stash {
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
cmd: Stash,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Remote {
|
||||||
|
#[structopt(name = "add")]
|
||||||
|
Add { name: String, url: String },
|
||||||
|
#[structopt(name = "remove")]
|
||||||
|
Remove { name: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Stash {
|
||||||
|
#[structopt(name = "save")]
|
||||||
|
Save,
|
||||||
|
#[structopt(name = "pop")]
|
||||||
|
Pop,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sub_sub_cmd_with_option() {
|
||||||
|
fn make(args: &[&str]) -> Option<SubSubCmdWithOption> {
|
||||||
|
SubSubCmdWithOption::clap()
|
||||||
|
.get_matches_from_safe(args)
|
||||||
|
.ok()
|
||||||
|
.map(|m| SubSubCmdWithOption::from_clap(&m))
|
||||||
|
}
|
||||||
|
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"]));
|
||||||
|
}
|
140
tests/options.rs
Normal file
140
tests/options.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_option() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", long = "arg")]
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: 42 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: 42 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "42"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: 42 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--arg", "42"]))
|
||||||
|
);
|
||||||
|
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a42", "-a24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_option() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a")]
|
||||||
|
arg: Option<i32>,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: Some(42) },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: None },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a42", "-a24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn option_with_default() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", default_value = "42")]
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: 24 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: 42 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a42", "-a24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn option_with_raw_default() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", raw(default_value = r#""42""#))]
|
||||||
|
arg: i32,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: 24 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: 42 },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
Opt::clap()
|
||||||
|
.get_matches_from_safe(&["test", "-a42", "-a24"])
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn options() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", long = "arg")]
|
||||||
|
arg: Vec<i32>,
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: vec![24] },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: vec![] },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: vec![24, 42] },
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24", "--arg", "42"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empy_default_value() {
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(short = "a", default_value = "")]
|
||||||
|
arg: String,
|
||||||
|
}
|
||||||
|
assert_eq!(Opt { arg: "".into() }, Opt::from_iter(&["test"]));
|
||||||
|
assert_eq!(
|
||||||
|
Opt { arg: "foo".into() },
|
||||||
|
Opt::from_iter(&["test", "-afoo"])
|
||||||
|
);
|
||||||
|
}
|
29
tests/privacy.rs
Normal file
29
tests/privacy.rs
Normal 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
mod options {
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
pub struct Options {
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
pub subcommand: ::subcommands::SubCommand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod subcommands {
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
pub enum SubCommand {
|
||||||
|
#[structopt(name = "foo", about = "foo")]
|
||||||
|
Foo {
|
||||||
|
#[structopt(help = "foo")]
|
||||||
|
bars: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
121
tests/raw_attributes.rs
Normal file
121
tests/raw_attributes.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::clap::AppSettings;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
// Check if the global settings compile
|
||||||
|
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||||
|
#[structopt(raw(global_settings = "&[AppSettings::ColoredHelp]"))]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(
|
||||||
|
long = "x",
|
||||||
|
raw(
|
||||||
|
display_order = "2",
|
||||||
|
next_line_help = "true",
|
||||||
|
default_value = r#""0""#,
|
||||||
|
require_equals = "true"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
x: i32,
|
||||||
|
|
||||||
|
#[structopt(short = "l", long = "level", raw(aliases = r#"&["set-level", "lvl"]"#))]
|
||||||
|
level: String,
|
||||||
|
|
||||||
|
#[structopt(long = "values")]
|
||||||
|
values: Vec<i32>,
|
||||||
|
|
||||||
|
#[structopt(name = "FILE", raw(requires_if = r#""FILE", "values""#))]
|
||||||
|
files: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_slice() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
x: 0,
|
||||||
|
level: "1".to_string(),
|
||||||
|
files: Vec::new(),
|
||||||
|
values: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
x: 0,
|
||||||
|
level: "1".to_string(),
|
||||||
|
files: Vec::new(),
|
||||||
|
values: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--level", "1"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
x: 0,
|
||||||
|
level: "1".to_string(),
|
||||||
|
files: Vec::new(),
|
||||||
|
values: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--set-level", "1"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
x: 0,
|
||||||
|
level: "1".to_string(),
|
||||||
|
files: Vec::new(),
|
||||||
|
values: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--lvl", "1"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_multi_args() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
x: 0,
|
||||||
|
level: "1".to_string(),
|
||||||
|
files: vec!["file".to_string()],
|
||||||
|
values: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "file"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
x: 0,
|
||||||
|
level: "1".to_string(),
|
||||||
|
files: vec!["FILE".to_string()],
|
||||||
|
values: vec![1],
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--values", "1", "--", "FILE"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_multi_args_fail() {
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--", "FILE"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw_bool() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt {
|
||||||
|
x: 1,
|
||||||
|
level: "1".to_string(),
|
||||||
|
files: vec![],
|
||||||
|
values: vec![],
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--x=1"]))
|
||||||
|
);
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--x", "1"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
225
tests/subcommands.rs
Normal file
225
tests/subcommands.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Opt {
|
||||||
|
#[structopt(name = "fetch", about = "Fetch stuff from GitHub.")]
|
||||||
|
Fetch {
|
||||||
|
#[structopt(long = "all")]
|
||||||
|
all: bool,
|
||||||
|
#[structopt(short = "f", long = "force")]
|
||||||
|
/// Overwrite local branches.
|
||||||
|
force: bool,
|
||||||
|
repo: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[structopt(name = "add")]
|
||||||
|
Add {
|
||||||
|
#[structopt(short = "i", long = "interactive")]
|
||||||
|
interactive: bool,
|
||||||
|
#[structopt(short = "v", long = "verbose")]
|
||||||
|
verbose: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fetch() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt::Fetch {
|
||||||
|
all: true,
|
||||||
|
force: false,
|
||||||
|
repo: "origin".to_string()
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "--all", "origin"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt::Fetch {
|
||||||
|
all: false,
|
||||||
|
force: true,
|
||||||
|
repo: "origin".to_string()
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "-f", "origin"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt::Add {
|
||||||
|
interactive: false,
|
||||||
|
verbose: false
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt::Add {
|
||||||
|
interactive: true,
|
||||||
|
verbose: true
|
||||||
|
},
|
||||||
|
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add", "-i", "-v"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_parse() {
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "badcmd", "-i", "-v"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badoption"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Opt2 {
|
||||||
|
#[structopt(name = "do-something")]
|
||||||
|
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::from_clap(&Opt2::clap().get_matches_from(&["test", "do-something", "blah"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Opt3 {
|
||||||
|
#[structopt(name = "add")]
|
||||||
|
Add,
|
||||||
|
#[structopt(name = "init")]
|
||||||
|
Init,
|
||||||
|
#[structopt(name = "fetch")]
|
||||||
|
Fetch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_null_commands() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt3::Add,
|
||||||
|
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "add"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt3::Init,
|
||||||
|
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "init"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt3::Fetch,
|
||||||
|
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "fetch"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
#[structopt(about = "Not shown")]
|
||||||
|
struct Add {
|
||||||
|
file: String,
|
||||||
|
}
|
||||||
|
/// Not shown
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
struct Fetch {
|
||||||
|
remote: String,
|
||||||
|
}
|
||||||
|
#[derive(StructOpt, PartialEq, Debug)]
|
||||||
|
enum Opt4 {
|
||||||
|
/// Not shown
|
||||||
|
#[structopt(name = "add", about = "Add a file")]
|
||||||
|
Add(Add),
|
||||||
|
#[structopt(name = "init")]
|
||||||
|
Init,
|
||||||
|
/// download history from remote
|
||||||
|
#[structopt(name = "fetch")]
|
||||||
|
Fetch(Fetch),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_commands() {
|
||||||
|
assert_eq!(
|
||||||
|
Opt4::Add(Add {
|
||||||
|
file: "f".to_string()
|
||||||
|
}),
|
||||||
|
Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "add", "f"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt4::Init,
|
||||||
|
Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "init"]))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Opt4::Fetch(Fetch {
|
||||||
|
remote: "origin".to_string()
|
||||||
|
}),
|
||||||
|
Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "fetch", "origin"]))
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
Opt4::clap().write_long_help(&mut output).unwrap();
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
|
||||||
|
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(StructOpt, Debug, PartialEq)]
|
||||||
|
pub enum Opt {
|
||||||
|
#[structopt(name = "daemon")]
|
||||||
|
Daemon(DaemonCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug, PartialEq)]
|
||||||
|
pub enum DaemonCommand {
|
||||||
|
#[structopt(name = "start")]
|
||||||
|
Start,
|
||||||
|
#[structopt(name = "stop")]
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
let result = Opt::clap().get_matches_from_safe(&["test", "daemon"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
let result = Opt::from_iter(&["test", "daemon", "start"]);
|
||||||
|
assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flatten_enum() {
|
||||||
|
#[derive(StructOpt, Debug, PartialEq)]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
sub_cmd: SubCmd,
|
||||||
|
}
|
||||||
|
#[derive(StructOpt, Debug, PartialEq)]
|
||||||
|
enum SubCmd {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(Opt::from_iter_safe(&["test"]).is_err());
|
||||||
|
assert_eq!(
|
||||||
|
Opt::from_iter(&["test", "Foo"]),
|
||||||
|
Opt {
|
||||||
|
sub_cmd: SubCmd::Foo
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue