Merge remote-tracking branch 'origin-structopt/master'

This commit is contained in:
Kevin K 2018-07-02 10:54:26 -04:00
commit 61edf0f2cc
No known key found for this signature in database
GPG key ID: 17218E4B3692F01A
39 changed files with 4059 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
target
Cargo.lock
*~

17
.travis.yml Normal file
View 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
View file

@ -0,0 +1,156 @@
# v0.2.10 (2018-06-07)
* 1.21.0 is the minimum required rustc version by
[@TeXitoi](https://github.com/TeXitoi)
# v0.2.9 (2018-06-05)
* Fix a bug when using `flatten` by
[@fbenkstein](https://github.com/fbenkstein)
* Update syn, quote and proc_macro2 by
[@TeXitoi](https://github.com/TeXitoi)
* Fix a regression when there is multiple authors by
[@windwardly](https://github.com/windwardly)
# v0.2.8 (2018-04-28)
* Add `StructOpt::from_iter_safe()`, which returns an `Error` instead of
killing the program when it fails to parse, or parses one of the
short-circuiting flags. ([#98](https://github.com/TeXitoi/structopt/pull/98)
by [@quodlibetor](https://github.com/quodlibetor))
* Allow users to enable `clap` features independently by
[@Kerollmops](https://github.com/Kerollmops)
* Fix a bug when flattening an enum
([#103](https://github.com/TeXitoi/structopt/pull/103) by
[@TeXitoi](https://github.com/TeXitoi)
# v0.2.7 (2018-04-12)
* Add flattening, the insertion of options of another StructOpt struct
into another ([#92](https://github.com/TeXitoi/structopt/pull/92))
by [@birkenfeld](https://github.com/birkenfeld)
* Fail compilation when using `default_value` or `required` with
`Option` ([#88](https://github.com/TeXitoi/structopt/pull/88)) by
[@Kerollmops](https://github.com/Kerollmops)
# v0.2.6 (2018-03-31)
* Fail compilation when using `default_value` or `required` with `bool` ([#80](https://github.com/TeXitoi/structopt/issues/80)) by [@TeXitoi](https://github.com/TeXitoi)
* Fix compilation with `#[deny(warnings)]` with the `!` type (https://github.com/rust-lang/rust/pull/49039#issuecomment-376398999) by [@TeXitoi](https://github.com/TeXitoi)
* Improve first example in the documentation ([#82](https://github.com/TeXitoi/structopt/issues/82)) by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.5 (2018-03-07)
* Work around breakage when `proc-macro2`'s nightly feature is enabled. ([#77](https://github.com/Texitoi/structopt/pull/77) and [proc-macro2#67](https://github.com/alexcrichton/proc-macro2/issues/67)) by [@fitzgen](https://github.com/fitzgen)
# v0.2.4 (2018-02-25)
* Fix compilation with `#![deny(missig_docs]` ([#74](https://github.com/TeXitoi/structopt/issues/74)) by [@TeXitoi](https://github.com/TeXitoi)
* Fix [#76](https://github.com/TeXitoi/structopt/issues/76) by [@TeXitoi](https://github.com/TeXitoi)
* Re-licensed to Apache-2.0/MIT by [@CAD97](https://github.com/cad97)
# v0.2.3 (2018-02-16)
* An empty line in a doc comment will result in a double linefeed in the generated about/help call by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.2 (2018-02-12)
* Fix [#66](https://github.com/TeXitoi/structopt/issues/66) by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.1 (2018-02-11)
* Fix a bug around enum tuple and the about message in the global help by [@TeXitoi](https://github.com/TeXitoi)
* Fix [#65](https://github.com/TeXitoi/structopt/issues/65) by [@TeXitoi](https://github.com/TeXitoi)
# v0.2.0 (2018-02-10)
## Breaking changes
### Don't special case `u64` by [@SergioBenitez](https://github.com/SergioBenitez)
If you are using a `u64` in your struct to get the number of occurence of a flag, you should now add `parse(from_occurrences)` on the flag.
For example
```rust
#[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
View 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
View file

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

21
LICENSE-MIT Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

133
README.md Normal file
View 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
View 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
View 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);
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}

View 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
View 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);
}

View 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
View 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)?))
}
}

View 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

View file

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

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View 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
View 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
View 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
);
}

View 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."));
}

View 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
View 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"])
);
}

View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,29 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[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
View 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
View 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
}
);
}