mirror of
https://github.com/clap-rs/clap
synced 2024-12-15 07:12:32 +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