Ported all the commits from structopt (#23)

* Automatic naming of fields and subcommands (#143)

* Introduce smarter parsing of doc comments. (#146)

* Fix invalid structopt syntax error message (#154)

There was a typo preventing the probematic attr to be shown to the user.

* Fix spelling: occurences -> occurrences, (#158)

was found in comments and code, but required no user-facing API change.

* Remove line-ending periods from short help. (#161)

* Fix #168

* Support Option<Option<T>> field types (#190)

* Support Option<Vec<T>> field type (#191)

* Fix use of deprecated function

* Fix some clippy lints

* Update deprecated function and provide more info about the parsing error (#193)

* Improve ChangeLog as suggested by @ErichDonGubler (#194)

* [Casing] Change default from verbatim to kebab. (#204)

.. fixes TeXitoi/structopt#202

* Use trybuild for testing expected proc macro errors (#205)

* Custom attributes parser (#198)

* update README.md for 0.3

fix #208

* Small fixes: clippy and typos (#215)

* Add example for environment variables (#160) (#216)

* Support skipping struct fields (#213)

* Now error messages highlight the error location (#225)

* Minor fixes

* Change behavior of `about/author/version` and ad `no_version`

* Emit error about `raw` removal

* Update changelog

* Update keyvalue example (#234)

* Update documentation and changelog (#236)

* Update dependencies (#227)

* Bump minimum rustc version to 1.36

* Fix issues

* Fix structopt-derive permissions (#239)

* Fix #245 (#246)

* Emit dummy impls on error (#248)

* Fix changelog example (#250)

* Do not call .verison() when CARGO_PKG_VERSION is not set

* Update and improve docs

* Propagate span info from origin code to generated code

Most of `quote!` invocations are replaced with `quote_spanned!` ones.
Now everywhere - sometimes it's pointless, sometimes we don't have
any meaningless location to toke a span from, sometimes I just can't
workaround the current implementation - too much changes.

* Fix nightly tests

* Do not mangle `author` string inside `<...>`

* Support `skip = value` syntax

* Fix code formatting

* Fix nightly tests

* Run ui tests only on stable

* Add from_flag parser (#271)

* Clarify docs and error messages (#277)

* Fix parse for OptionVec (#279)

ref pull #191

* Fix #269 and #265 (#278)

* Pass the try_from_str functions a &str instead of a &String. (#282)

In most cases this doesn't matter, as &String is coerced to a &str, but
this fails for generic functions like CString::new.

* Add an example of a negative flag (i.e. --no-verbose)

Question from https://github.com/TeXitoi/structopt/issues/280

* Fix #283 (#284)

Fix #283

* Add `examples/README.md` and do some cleanup

* Handle special types correctly

* cargo clippy

* Handle inter-expansion top-level args properly

* Cleanup tests

* Update proc-macro-error to v0.4

* Offer helpful suggestion on `raw(...)` error

* Add `after_help` example

* Prohibit positional `bool` args

* Add tests/utils.rs

* fixed typo, removed misleading doc

* Remove CHANGELOG additions

* Rust 2018

* Addressed review

Co-authored-by: rnd <bruno.kirschner@online.de>
Co-authored-by: Robin Lambertz <github@roblab.la>
Co-authored-by: florianjacob <accounts+github@florianjacob.de>
Co-authored-by: Ted Driggs <ted.driggs@outlook.com>
Co-authored-by: Guillaume P. <TeXitoi@users.noreply.github.com>
Co-authored-by: Ivan Veselov <veselov@gmail.com>
Co-authored-by: Owen Walpole <owenthewizard@hotmail.com>
Co-authored-by: Robin Stocker <robin.stocker@gmail.com>
Co-authored-by: CreepySkeleton <creepy-skeleton@yandex.ru>
Co-authored-by: Ophir LOJKINE <ophir.lojkine@auto-grid.com>
Co-authored-by: kpcyrd <git@rxv.cc>
Co-authored-by: Luiz F. A. de Prá <luizdepra@users.noreply.github.com>
Co-authored-by: Andy Weiss <wvvwwvw@gmail.com>
Co-authored-by: xiaoniu-578fa6bff964d005 <32661032+xiaoniu-578fa6bff964d005@users.noreply.github.com>
Co-authored-by: Mara Bos <m-ou.se@m-ou.se>
Co-authored-by: Renê Couto e Silva <31329678+csrene@users.noreply.github.com>
This commit is contained in:
Pavan Kumar Sunkara 2020-01-07 15:47:23 +05:30 committed by Dylan DPC
parent 2622165be3
commit 0352bb30c8
120 changed files with 4074 additions and 925 deletions

View file

@ -1,6 +1,7 @@
[package]
name = "clap_derive"
version = "0.3.0"
edition = "2018"
authors = [
"Guillaume Pinot <texitoi@texitoi.eu>",
"Kevin K. <kbknapp@gmail.com>",
@ -22,17 +23,20 @@ travis-ci = { repository = "clap-rs/clap_derive" }
appveyor = { repository = "https://github.com/clap-rs/clap_derive", service = "github" }
[dependencies]
syn = "0.15.39"
quote = "0.6"
proc-macro2 = "0.4"
clippy = {version = "0.0.174", optional = true }
syn = { version = "1", features = ["full"] }
quote = "1"
proc-macro2 = "1"
heck = "0.3.0"
proc-macro-error = "0.4.3"
[dev-dependencies]
clap = { git = "https://github.com/clap-rs/clap", branch = "master"} # ONLY FOR INITIAL DEVELOPMENT...change to real crates.io ver for rlease!
trybuild = "1.0.5"
rustversion = "0.1"
[features]
default = []
nightly = []
lints = ["clippy"]
lints = []
debug = []
doc = []

View file

@ -34,30 +34,33 @@ use clap::Clap;
#[clap(name = "basic")]
struct Opt {
// A flag, true if used in the command line. Note doc comment will
// be used for the help message of the flag.
// be used for the help message of the flag. The name of the
// argument will be, by default, based on the name of the field.
/// Activate debug mode
#[clap(short = "d", long = "debug")]
#[clap(short, long)]
debug: bool,
// The number of occurences of the `v/verbose` flag
// The number of occurrences of the `v/verbose` flag
/// Verbose mode (-v, -vv, -vvv, etc.)
#[clap(short = "v", long = "verbose", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
verbose: u8,
/// Set speed
#[clap(short = "s", long = "speed", default_value = "42")]
#[clap(short, long, default_value = "42")]
speed: f64,
/// Output file
#[clap(short = "o", long = "output", parse(from_os_str))]
#[clap(short, long, parse(from_os_str))]
output: PathBuf,
// the long option will be translated by default to kebab case,
// i.e. `--nb-cars`.
/// Number of cars
#[clap(short = "c", long = "nb-cars")]
#[clap(short = "c", long)]
nb_cars: Option<i32>,
/// admin_level to consider
#[clap(short = "l", long = "level")]
#[clap(short, long)]
level: Vec<String>,
/// Files to process
@ -67,7 +70,7 @@ struct Opt {
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
println!("{:#?}", opt);
}
```
@ -82,12 +85,12 @@ USAGE:
For more information try --help
$ ./basic --help
basic 0.2.0
Guillaume Pinot <texitoi@texitoi.eu>
basic 0.3.0
Guillaume Pinot <texitoi@texitoi.eu>, others
A basic example
USAGE:
basic [FLAGS] [OPTIONS] --output <output> [--] [FILE]...
basic [FLAGS] [OPTIONS] --output <output> [--] [file]...
ARGS:
<FILE>... Files to process
@ -96,17 +99,44 @@ FLAGS:
-d, --debug Activate debug mode
-h, --help Prints help information
-V, --version Prints version information
-v, --verbose Verbose mode
-v, --verbose Verbose mode (-v, -vv, -vvv, etc.)
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]
-l, --level <level>... admin_level to consider
-c, --nb-cars <nb-cars> Number of cars
-o, --output <output> Output file
-s, --speed <speed> Set speed [default: 42]
ARGS:
<file>... Files to process
$ ./basic -o foo.txt
Opt { debug: false, verbose: 0, speed: 42, output: "foo.txt", car: None, level: [], files: [] }
Opt {
debug: false,
verbose: 0,
speed: 42.0,
output: "foo.txt",
nb_cars: None,
level: [],
files: [],
}
$ ./basic -o foo.txt -dvvvs 1337 -l alice -l bob --nb-cars 4 bar.txt baz.txt
Opt { debug: true, verbose: 3, speed: 1337, output: "foo.txt", nb_cars: Some(4), level: ["alice", "bob"], files: ["bar.txt", "baz.txt"] }
Opt {
debug: true,
verbose: 3,
speed: 1337.0,
output: "foo.txt",
nb_cars: Some(
4,
),
level: [
"alice",
"bob",
],
files: [
"bar.txt",
"baz.txt",
],
}
```
## clap_derive rustc version policy

78
examples/README.md Normal file
View file

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

19
examples/after_help.rs Normal file
View file

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

View file

@ -1,11 +1,11 @@
#[macro_use]
extern crate clap;
//! How to require presence of at least N values,
//! like `val1 val2 ... valN ... valM`.
use clap::Clap;
#[derive(Clap, Debug)]
struct Opt {
#[clap(raw(required = "true", min_values = "2"))]
#[clap(required = true, min_values = 2)]
foos: Vec<String>,
}

View file

@ -1,19 +1,4 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
//! A somewhat comprehensive example of a typical `StructOpt` usage.use
use clap::Clap;
use std::path::PathBuf;
@ -23,30 +8,33 @@ use std::path::PathBuf;
#[clap(name = "basic")]
struct Opt {
// A flag, true if used in the command line. Note doc comment will
// be used for the help message of the flag.
// be used for the help message of the flag. The name of the
// argument will be, by default, based on the name of the field.
/// Activate debug mode
#[clap(short = "d", long = "debug")]
#[clap(short, long)]
debug: bool,
// The number of occurences of the `v/verbose` flag
// The number of occurrences of the `v/verbose` flag
/// Verbose mode (-v, -vv, -vvv, etc.)
#[clap(short = "v", long = "verbose", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
verbose: u8,
/// Set speed
#[clap(short = "s", long = "speed", default_value = "42")]
#[clap(short, long, default_value = "42")]
speed: f64,
/// Output file
#[clap(short = "o", long = "output", parse(from_os_str))]
#[clap(short, long, parse(from_os_str))]
output: PathBuf,
// the long option will be translated by default to kebab case,
// i.e. `--nb-cars`.
/// Number of cars
#[clap(short = "c", long = "nb-cars")]
#[clap(short = "c", long)]
nb_cars: Option<i32>,
/// admin_level to consider
#[clap(short = "l", long = "level")]
#[clap(short, long)]
level: Vec<String>,
/// Files to process
@ -56,5 +44,5 @@ struct Opt {
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
println!("{:#?}", opt);
}

View file

@ -16,15 +16,12 @@
#![deny(missing_docs)]
#[macro_use]
extern crate clap;
use clap::Clap;
/// The options
#[derive(Clap, Debug, PartialEq)]
pub struct Opt {
#[clap(short = "v")]
#[clap(short)]
verbose: bool,
#[clap(subcommand)]
cmd: Option<Cmd>,
@ -38,7 +35,7 @@ pub enum Cmd {
/// command B
B {
/// Alice?
#[clap(short = "a")]
#[clap(short)]
alice: bool,
},
/// command C
@ -48,7 +45,7 @@ pub enum Cmd {
/// The options for C
#[derive(Clap, Debug, PartialEq)]
pub struct COpt {
#[clap(short = "b")]
#[clap(short)]
bob: bool,
}

74
examples/doc_comments.rs Normal file
View file

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

View file

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

View file

@ -1,5 +1,4 @@
#[macro_use]
extern crate clap;
//! How to extract subcommands' args into external structs.
use clap::Clap;

26
examples/env.rs Normal file
View file

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

View file

@ -1,41 +1,51 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
//! Somewhat complex example of usage of structopt.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "example", about = "An example of StructOpt usage.")]
#[clap(name = "example")]
/// An example of clap_derive usage.
struct Opt {
/// A flag, true if used in the command line.
#[clap(short = "d", long = "debug", help = "Activate debug mode")]
// A flag, true if used in the command line.
#[clap(short, long)]
/// Activate debug mode
debug: bool,
/// An argument of type float, with a default value.
#[clap(short = "s", long = "speed", help = "Set speed", default_value = "42")]
// An argument of type float, with a default value.
#[clap(short, long, default_value = "42")]
/// Set speed
speed: f64,
/// Needed parameter, the first on the command line.
#[clap(help = "Input file")]
// Needed parameter, the first on the command line.
/// Input file
input: String,
/// An optional parameter, will be `None` if not present on the
/// command line.
#[clap(help = "Output file, stdout if not present")]
// An optional parameter, will be `None` if not present on the
// command line.
/// Output file, stdout if not present
output: Option<String>,
// An optional parameter with optional value, will be `None` if
// not present on the command line, will be `Some(None)` if no
// argument is provided (i.e. `--log`) and will be
// `Some(Some(String))` if argument is provided (e.g. `--log
// log.txt`).
#[clap(long)]
#[allow(clippy::option_option)]
/// Log file, stdout if no file, no logging if not present
log: Option<Option<String>>,
// An optional list of values, will be `None` if not present on
// the command line, will be `Some(vec![])` if no argument is
// provided (i.e. `--optv`) and will be `Some(Some(String))` if
// argument list is provided (e.g. `--optv a b c`).
#[clap(long)]
optv: Option<Vec<String>>,
// Skipped option: it won't be parsed and will be filled with the
// default value for its type (in this case it'll be an empty string).
#[clap(skip)]
skipped: String,
}
fn main() {

View file

@ -1,21 +1,25 @@
#[macro_use]
extern crate clap;
//! How to use flattening.
use clap::Clap;
#[derive(Clap, Debug)]
struct Cmdline {
#[clap(short = "v", help = "switch on verbosity")]
/// switch verbosity on
#[clap(short)]
verbose: bool,
#[clap(flatten)]
daemon_opts: DaemonOpts,
}
#[derive(Clap, Debug)]
struct DaemonOpts {
#[clap(short = "u", help = "daemon user")]
/// daemon user
#[clap(short)]
user: String,
#[clap(short = "g", help = "daemon group")]
/// daemon group
#[clap(short)]
group: String,
}

View file

@ -1,10 +1,7 @@
//! `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 clap;
//! Documentation can be added either through doc comments or
//! `help`/`about` attributes.
use clap::Clap;
@ -12,22 +9,20 @@ use clap::Clap;
#[clap(name = "git")]
/// the stupid content tracker
enum Opt {
#[clap(name = "fetch")]
/// fetch branches from remote repository
Fetch {
#[clap(long = "dry-run")]
#[clap(long)]
dry_run: bool,
#[clap(long = "all")]
#[clap(long)]
all: bool,
#[clap(default_value = "origin")]
repository: String,
},
#[clap(name = "add")]
/// add files to the staging area
#[clap(override_help = "add files to the staging area")]
Add {
#[clap(short = "i")]
#[clap(short)]
interactive: bool,
#[clap(short = "a")]
#[clap(short)]
all: bool,
files: Vec<String>,
},

View file

@ -1,36 +1,28 @@
// A functional translation of the example at
// https://docs.rs/clap/2.31.2/clap/struct.App.html#method.group
#[macro_use]
extern crate clap;
//! How to use `clap::Arg::group`
use clap::{ArgGroup, Clap};
// 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(Clap, Debug)]
#[clap(raw(group = "vers_arg_group()"))]
#[clap(group = ArgGroup::with_name("verb").required(true))]
struct Opt {
/// set the version manually
#[clap(long = "set-ver", group = "vers")]
set_ver: Option<String>,
/// auto increase major
#[clap(long = "major", group = "vers")]
major: bool,
/// auto increase minor
#[clap(long = "minor", group = "vers")]
minor: bool,
/// auto increase patch
#[clap(long = "patch", group = "vers")]
patch: bool,
/// Set a custom HTTP verb
#[clap(long, group = "verb")]
method: Option<String>,
/// HTTP GET
#[clap(long, group = "verb")]
get: bool,
/// HTTP HEAD
#[clap(long, group = "verb")]
head: bool,
/// HTTP POST
#[clap(long, group = "verb")]
post: bool,
/// HTTP PUT
#[clap(long, group = "verb")]
put: bool,
/// HTTP DELETE
#[clap(long, group = "verb")]
delete: bool,
}
fn main() {

View file

@ -1,10 +1,10 @@
#[macro_use]
extern crate clap;
//! How to parse "key=value" pairs with structopt.
use clap::Clap;
use std::error::Error;
fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<Error>>
/// Parse a single key-value pair
fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error>>
where
T: std::str::FromStr,
T::Err: Error + 'static,
@ -19,7 +19,14 @@ where
#[derive(Clap, Debug)]
struct Opt {
#[clap(short = "D", parse(try_from_str = "parse_key_val"))]
// number_of_values = 1 forces the user to repeat the -D option for each key-value pair:
// my_program -D a=1 -D b=2
// Without number_of_values = 1 you can do:
// my_program -D a=1 b=2
// but this makes adding an argument after the values impossible:
// my_program -D a=1 -D b=2 my_input_file
// becomes invalid.
#[clap(short = "D", parse(try_from_str = parse_key_val), number_of_values = 1)]
defines: Vec<(String, i32)>,
}

15
examples/negative_flag.rs Normal file
View file

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

View file

@ -1,15 +1,12 @@
#[macro_use]
extern crate clap;
//! How to completely remove version.
use clap::{AppSettings, Clap};
#[derive(Clap, Debug)]
#[clap(
name = "no_version",
about = "",
version = "",
author = "",
raw(global_setting = "AppSettings::DisableVersion")
no_version,
global_setting = AppSettings::DisableVersion
)]
struct Opt {}

View file

@ -1,43 +0,0 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::{AppSettings, Clap};
/// An example of raw attributes
#[derive(Clap, Debug)]
#[clap(raw(global_setting = "AppSettings::ColoredHelp"))]
#[clap(raw(global_setting = "AppSettings::VersionlessSubcommands"))]
struct Opt {
/// Output file
#[clap(short = "o", long = "output")]
output: String,
/// admin_level to consider
#[clap(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`.
#[clap(name = "FILE", raw(requires_if = r#""FILE", "level""#))]
files: Vec<String>,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

74
examples/rename_all.rs Normal file
View file

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

View file

@ -1,31 +0,0 @@
#[macro_use]
extern crate clap;
use clap::Clap;
#[derive(Clap, Debug)]
struct Opt {
/// Set a custom HTTP verb
#[clap(long = "method", group = "verb")]
method: Option<String>,
/// HTTP GET; default if no other HTTP verb is selected
#[clap(long = "get", group = "verb")]
get: bool,
/// HTTP HEAD
#[clap(long = "head", group = "verb")]
head: bool,
/// HTTP POST
#[clap(long = "post", group = "verb")]
post: bool,
/// HTTP PUT
#[clap(long = "put", group = "verb")]
put: bool,
/// HTTP DELETE
#[clap(long = "delete", group = "verb")]
delete: bool,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

47
examples/skip.rs Normal file
View file

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

View file

@ -1,17 +1,16 @@
#[macro_use]
extern crate clap;
//! How to assign some aliases to subcommands
use clap::{AppSettings, Clap};
#[derive(Clap, Debug)]
// https://docs.rs/clap/2/clap/enum.AppSettings.html#variant.InferSubcommands
#[clap(raw(setting = "AppSettings::InferSubcommands"))]
#[clap(setting = AppSettings::InferSubcommands)]
enum Opt {
// https://docs.rs/clap/2/clap/struct.App.html#method.alias
#[clap(name = "foo", alias = "foobar")]
#[clap(alias = "foobar")]
Foo,
// https://docs.rs/clap/2/clap/struct.App.html#method.aliases
#[clap(name = "bar", raw(aliases = r#"&["baz", "fizz"]"#))]
#[clap(aliases = &["baz", "fizz"])]
Bar,
}

41
examples/true_or_false.rs Normal file
View file

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

View file

@ -8,12 +8,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use proc_macro2;
use quote;
// use quote;
use syn;
use syn::punctuated;
use syn::token;
// use syn::punctuated;
// use syn::token;
pub fn derive_arg_enum(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
pub fn derive_arg_enum(_ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
unimplemented!()
// let from_str_block = impl_from_str(ast)?;

View file

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

View file

@ -12,62 +12,62 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use proc_macro2;
use syn;
use syn::punctuated;
use syn::token;
use proc_macro_error::{abort, abort_call_site, set_dummy};
use syn::{self, punctuated, spanned::Spanned, token};
use derives;
use derives::attrs::{Attrs, Kind, Parser, Ty};
use derives::from_argmatches;
use derives::into_app;
use super::{from_argmatches, into_app, sub_type, Attrs, Kind, Name, ParserKind, Ty};
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an app.
fn gen_app_augmentation(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
app_var: &syn::Ident,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let 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, derives::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(
::clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
Some(quote! {
let #app_var = <#subcmd_type>::augment_app( #app_var );
#required
})
let mut subcmds = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(&field, parent_attribute.casing());
let kind = attrs.kind();
if let Kind::Subcommand(ty) = &*kind {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let required = if **ty == Ty::Option {
quote!()
} else {
None
}
})
.collect();
quote_spanned! { kind.span()=>
let #app_var = #app_var.setting(
::clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
assert!(
subcmds.len() <= 1,
"cannot have more than one nested subcommand"
);
let span = field.span();
let ts = quote! {
let #app_var = <#subcmd_type>::augment_app( #app_var );
#required
};
Some((span, ts))
} else {
None
}
});
let subcmd = subcmds.next().map(|(_, ts)| ts);
if let Some((span, _)) = subcmds.next() {
abort!(
span,
"multiple subcommand sets are not allowed, that's the second"
);
}
let args = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(field);
match attrs.kind() {
Kind::Subcommand(_) => None,
let attrs = Attrs::from_field(field, parent_attribute.casing());
let kind = attrs.kind();
match &*kind {
Kind::Subcommand(_) | Kind::Skip(_) => None,
Kind::FlattenStruct => {
let ty = &field.ty;
Some(quote! {
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty>::augment_app(#app_var);
let #app_var = if <#ty>::is_subcommand() {
#app_var.setting(::clap::AppSettings::SubcommandRequiredElseHelp)
@ -77,41 +77,85 @@ fn gen_app_augmentation(
})
}
Kind::Arg(ty) => {
let convert_type = match ty {
Ty::Vec | Ty::Option => derives::sub_type(&field.ty).unwrap_or(&field.ty),
let convert_type = match **ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
Ty::OptionOption | Ty::OptionVec => {
sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
}
_ => &field.ty,
};
let occurences = attrs.parser().0 == Parser::FromOccurrences;
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let validator = match *attrs.parser() {
(Parser::TryFromStr, ref f) => quote! {
let parser = attrs.parser();
let func = &parser.func;
let validator = match *parser.kind {
ParserKind::TryFromStr => quote_spanned! { func.span()=>
.validator(|s| {
#f(&s)
#func(s.as_str())
.map(|_: #convert_type| ())
.map_err(|e| e.to_string())
})
},
(Parser::TryFromOsStr, ref f) => quote! {
.validator_os(|s| #f(&s).map(|_: #convert_type| ()))
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
.validator_os(|s| #func(&s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
// @TODO remove unneccessary builders
let modifier = match ty {
let modifier = match **ty {
Ty::Bool => quote!(),
Ty::Option => quote!( .takes_value(true) #validator ),
Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ),
Ty::Other if occurences => quote!( .multiple_occurrences(true) ),
Ty::Option => quote_spanned! { ty.span()=>
.takes_value(true)
#validator
},
Ty::OptionOption => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(false)
.min_values(0)
.max_values(1)
#validator
},
Ty::OptionVec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
.min_values(0)
#validator
},
Ty::Vec => quote_spanned! { ty.span()=>
.takes_value(true)
.multiple(true)
#validator
},
Ty::Other if occurrences => quote_spanned! { ty.span()=>
.multiple_occurrences(true)
},
Ty::Other if flag => quote_spanned! { ty.span()=>
.takes_value(false)
.multiple(false)
},
Ty::Other => {
let required = !attrs.has_method("default_value");
quote!( .takes_value(true).required(#required) #validator )
quote_spanned! { ty.span()=>
.takes_value(true)
.required(#required)
#validator
}
}
};
let methods = attrs.methods();
let name = attrs.name();
Some(quote! {
let name = attrs.cased_name();
let methods = attrs.field_methods();
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg(
::clap::Arg::with_name(#name)
#modifier
@ -122,18 +166,21 @@ fn gen_app_augmentation(
}
});
let app_methods = parent_attribute.top_level_methods();
quote! {{
let #app_var = #app_var#app_methods;
#( #args )*
#( #subcmds )*
#subcmd
#app_var
}}
}
fn gen_augment_app_fn(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let app_var = syn::Ident::new("app", proc_macro2::Span::call_site());
let augmentation = gen_app_augmentation(fields, &app_var);
let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute);
quote! {
pub fn augment_app<'b>(
#app_var: ::clap::App<'b>
@ -145,19 +192,24 @@ fn gen_augment_app_fn(
fn gen_augment_app_for_enum(
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let subcommands = variants.iter().map(|variant| {
let name = variant.ident.to_string();
let attrs = Attrs::from_struct(&variant.attrs, name);
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
);
let app_var = syn::Ident::new("subcommand", proc_macro2::Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => gen_app_augmentation(&fields.named, &app_var),
Named(ref fields) => gen_app_augmentation(&fields.named, &app_var, &attrs),
Unit => quote!( #app_var ),
Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote! {
quote_spanned! { ty.span() =>
{
let #app_var = <#ty>::augment_app(#app_var);
if <#ty>::is_subcommand() {
@ -170,11 +222,12 @@ fn gen_augment_app_for_enum(
}
}
}
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
let name = attrs.name();
let from_attrs = attrs.methods();
let name = attrs.cased_name();
let from_attrs = attrs.top_level_methods();
quote! {
.subcommand({
let #app_var = ::clap::App::new(#name);
@ -184,11 +237,13 @@ fn gen_augment_app_for_enum(
}
});
let app_methods = parent_attribute.top_level_methods();
quote! {
pub fn augment_app<'b>(
app: ::clap::App<'b>
) -> ::clap::App<'b> {
app #( #subcommands )*
app #app_methods #( #subcommands )*
}
}
}
@ -196,21 +251,27 @@ fn gen_augment_app_for_enum(
fn gen_from_subcommand(
name: &syn::Ident,
variants: &punctuated::Punctuated<syn::Variant, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
use syn::Fields::*;
let match_arms = variants.iter().map(|variant| {
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.to_string());
let sub_name = attrs.name();
let attrs = Attrs::from_struct(
variant.span(),
&variant.attrs,
Name::Derived(variant.ident.clone()),
parent_attribute.casing(),
);
let sub_name = attrs.cased_name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
Named(ref fields) => from_argmatches::gen_constructor(&fields.named),
Named(ref fields) => from_argmatches::gen_constructor(&fields.named, &attrs),
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote!( ( <#ty as ::clap::FromArgMatches>::from_argmatches(matches) ) )
}
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
quote! {
@ -237,15 +298,17 @@ fn clap_impl_for_struct(
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
let into_app_impl = into_app::gen_into_app_impl_for_struct(name, attrs);
let augment_app_fn = gen_augment_app_fn(fields);
let from_argmatches_impl = from_argmatches::gen_from_argmatches_impl_for_struct(name, fields);
let into_app_impl_tokens = into_app_impl.tokens;
let augment_app_fn = gen_augment_app_fn(fields, &into_app_impl.attrs);
let from_argmatches_impl =
from_argmatches::gen_from_argmatches_impl_for_struct(name, fields, &into_app_impl.attrs);
let parse_fns = gen_parse_fns(name);
quote! {
#[allow(unused_variables)]
impl ::clap::Clap for #name { }
#into_app_impl
#into_app_impl_tokens
#from_argmatches_impl
@ -267,16 +330,17 @@ fn clap_impl_for_enum(
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
let into_app_impl = into_app::gen_into_app_impl_for_enum(name, attrs);
let augment_app_fn = gen_augment_app_for_enum(variants);
let into_app_impl_tokens = into_app_impl.tokens;
let augment_app_fn = gen_augment_app_for_enum(variants, &into_app_impl.attrs);
let from_argmatches_impl = from_argmatches::gen_from_argmatches_impl_for_enum(name);
let from_subcommand = gen_from_subcommand(name, variants);
let from_subcommand = gen_from_subcommand(name, variants, &into_app_impl.attrs);
let parse_fns = gen_parse_fns(name);
quote! {
#[allow(unused_variables)]
impl ::clap::Clap for #name { }
#into_app_impl
#into_app_impl_tokens
#from_argmatches_impl
@ -298,16 +362,37 @@ pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
let inner_impl = match input.data {
set_dummy(quote! {
impl ::clap::Clap for #struct_name {}
impl ::clap::IntoApp for #struct_name {
fn into_app<'b>() -> ::clap::App<'b> {
unimplemented!()
}
}
impl ::clap::FromArgMatches for #struct_name {
fn from_argmatches(m: &::clap::ArgMatches) -> Self {
unimplemented!()
}
}
impl #struct_name {
fn parse() -> Self {
unimplemented!();
}
}
});
match input.data {
Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => clap_impl_for_struct(struct_name, &fields.named, &input.attrs),
Enum(ref e) => clap_impl_for_enum(struct_name, &e.variants, &input.attrs),
_ => panic!("clap_derive only supports non-tuple structs and enums"),
};
quote!(#inner_impl)
_ => abort_call_site!("clap_derive only supports non-tuple structs and enums"),
}
}
fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream {

View file

@ -11,13 +11,15 @@
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use std::env;
use proc_macro2;
use syn;
use syn::punctuated;
use syn::spanned::Spanned as _;
use syn::token;
use derives::{self, Attrs, Kind, Parser, Ty};
use super::{spanned::Sp, sub_type, Attrs, Kind, Name, ParserKind, Ty, DEFAULT_CASING};
pub fn derive_from_argmatches(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
@ -27,7 +29,20 @@ pub fn derive_from_argmatches(input: &syn::DeriveInput) -> proc_macro2::TokenStr
Struct(syn::DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => gen_from_argmatches_impl_for_struct(struct_name, &fields.named),
}) => {
let name = env::var("CARGO_PKG_NAME")
.ok()
.unwrap_or_else(String::default);
let attrs = Attrs::from_struct(
proc_macro2::Span::call_site(),
&input.attrs,
Name::Assigned(syn::LitStr::new(&name, proc_macro2::Span::call_site())),
Sp::call_site(DEFAULT_CASING),
);
gen_from_argmatches_impl_for_struct(struct_name, &fields.named, &attrs)
}
// Enum(ref e) => clap_for_enum_impl(struct_name, &e.variants, &input.attrs),
_ => panic!("clap_derive only supports non-tuple structs"), // and enums"),
};
@ -38,8 +53,9 @@ pub fn derive_from_argmatches(input: &syn::DeriveInput) -> proc_macro2::TokenStr
pub fn gen_from_argmatches_impl_for_struct(
name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let from_argmatches_fn = gen_from_argmatches_fn_for_struct(name, fields);
let from_argmatches_fn = gen_from_argmatches_fn_for_struct(name, fields, parent_attribute);
quote! {
impl ::clap::FromArgMatches for #name {
@ -60,8 +76,9 @@ pub fn gen_from_argmatches_impl_for_struct(
pub fn gen_from_argmatches_fn_for_struct(
struct_name: &syn::Ident,
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let field_block = gen_constructor(fields);
let field_block = gen_constructor(fields, parent_attribute);
quote! {
fn from_argmatches(matches: &::clap::ArgMatches) -> Self {
@ -72,68 +89,124 @@ pub fn gen_from_argmatches_fn_for_struct(
pub fn gen_constructor(
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
parent_attribute: &Attrs,
) -> proc_macro2::TokenStream {
let fields = fields.iter().map(|field| {
let attrs = Attrs::from_field(field);
let attrs = Attrs::from_field(field, parent_attribute.casing());
let field_name = field.ident.as_ref().unwrap();
match attrs.kind() {
let kind = attrs.kind();
match &*attrs.kind() {
Kind::Subcommand(ty) => {
let subcmd_type = match (ty, derives::sub_type(&field.ty)) {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let unwrapper = match ty {
let unwrapper = match **ty {
Ty::Option => quote!(),
_ => quote!( .unwrap() ),
_ => quote_spanned!( ty.span()=> .unwrap() ),
};
quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper)
}
Kind::FlattenStruct => {
quote!(#field_name: ::clap::FromArgMatches::from_argmatches(matches))
quote_spanned! { kind.span()=>
#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper
}
}
Kind::FlattenStruct => quote_spanned! { kind.span()=>
#field_name: ::clap::FromArgMatches::from_argmatches(matches)
},
Kind::Skip(val) => match val {
None => quote_spanned!(kind.span()=> #field_name: Default::default()),
Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
},
Kind::Arg(ty) => {
use self::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()),
use self::ParserKind::*;
let parser = attrs.parser();
let func = &parser.func;
let span = parser.kind.span();
let (value_of, values_of, parse) = match *parser.kind {
FromStr => (
quote_spanned!(span=> value_of),
quote_spanned!(span=> values_of),
func.clone(),
),
(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()),
TryFromStr => (
quote_spanned!(span=> value_of),
quote_spanned!(span=> values_of),
quote_spanned!(func.span()=> |s| #func(s).unwrap()),
),
(FromOccurrences, ref f) => (quote!(occurrences_of), quote!(), f.clone()),
FromOsStr => (
quote_spanned!(span=> value_of_os),
quote_spanned!(span=> values_of_os),
func.clone(),
),
TryFromOsStr => (
quote_spanned!(span=> value_of_os),
quote_spanned!(span=> values_of_os),
quote_spanned!(func.span()=> |s| #func(s).unwrap()),
),
FromOccurrences => (
quote_spanned!(span=> occurrences_of),
quote!(),
func.clone(),
),
FromFlag => (quote!(), quote!(), func.clone()),
};
let 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! {
let flag = *attrs.parser().kind == ParserKind::FromFlag;
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let name = attrs.cased_name();
let field_value = match **ty {
Ty::Bool => quote_spanned! { ty.span()=>
matches.is_present(#name)
},
Ty::Option => quote_spanned! { ty.span()=>
matches.#value_of(#name)
.as_ref()
.map(#parse)
},
Ty::Vec => quote! {
Ty::OptionOption => quote_spanned! { ty.span()=>
if matches.is_present(#name) {
Some(matches.#value_of(#name).map(#parse))
} else {
None
}
},
Ty::OptionVec => quote_spanned! { ty.span()=>
if matches.is_present(#name) {
Some(matches.#values_of(#name)
.map(|v| v.map(#parse).collect())
.unwrap_or_else(Vec::new))
} else {
None
}
},
Ty::Vec => quote_spanned! { ty.span()=>
matches.#values_of(#name)
.map(|v| v.map(#parse).collect())
.unwrap_or_else(Vec::new)
},
Ty::Other if occurences => quote! {
Ty::Other if occurrences => quote_spanned! { ty.span()=>
#parse(matches.#value_of(#name))
},
Ty::Other => quote! {
Ty::Other if flag => quote_spanned! { ty.span()=>
#parse(matches.is_present(#name))
},
Ty::Other => quote_spanned! { ty.span()=>
matches.#value_of(#name)
.map(#parse)
.unwrap()
},
};
quote!( #field_name: #field_value )
quote_spanned!(field.span()=> #field_name: #field_value )
}
}
});

View file

@ -16,14 +16,16 @@ use std::env;
use proc_macro2;
use syn;
use derives::Attrs;
use super::{spanned::Sp, Attrs, GenOutput, Name, DEFAULT_CASING};
pub fn derive_into_app(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
let inner_impl = match input.data {
Struct(syn::DataStruct { .. }) => gen_into_app_impl_for_struct(struct_name, &input.attrs),
Struct(syn::DataStruct { .. }) => {
gen_into_app_impl_for_struct(struct_name, &input.attrs).tokens
}
// @TODO impl into_app for enums?
// Enum(ref e) => clap_for_enum_impl(struct_name, &e.variants, &input.attrs),
_ => panic!("clap_derive only supports non-tuple structs"), // and enums"),
@ -32,15 +34,13 @@ pub fn derive_into_app(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
quote!(#inner_impl)
}
pub fn gen_into_app_impl_for_struct(
name: &syn::Ident,
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
pub fn gen_into_app_impl_for_struct(name: &syn::Ident, attrs: &[syn::Attribute]) -> GenOutput {
let into_app_fn = gen_into_app_fn_for_struct(attrs);
let into_app_fn_tokens = into_app_fn.tokens;
quote! {
let tokens = quote! {
impl ::clap::IntoApp for #name {
#into_app_fn
#into_app_fn_tokens
}
impl<'b> Into<::clap::App<'b>> for #name {
@ -49,37 +49,54 @@ pub fn gen_into_app_impl_for_struct(
<#name as ::clap::IntoApp>::into_app()
}
}
};
GenOutput {
tokens,
attrs: into_app_fn.attrs,
}
}
pub fn gen_into_app_fn_for_struct(struct_attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
let app = gen_app_builder(struct_attrs);
quote! {
pub fn gen_into_app_fn_for_struct(struct_attrs: &[syn::Attribute]) -> GenOutput {
let gen = gen_app_builder(struct_attrs);
let app_tokens = gen.tokens;
let tokens = quote! {
fn into_app<'b>() -> ::clap::App<'b> {
Self::augment_app(#app)
Self::augment_app(#app_tokens)
}
};
GenOutput {
tokens,
attrs: gen.attrs,
}
}
pub fn gen_app_builder(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
let name = 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!(::clap::App::new(#name)#methods)
pub fn gen_app_builder(attrs: &[syn::Attribute]) -> GenOutput {
let name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
let attrs = Attrs::from_struct(
proc_macro2::Span::call_site(),
attrs,
Name::Assigned(syn::LitStr::new(&name, proc_macro2::Span::call_site())),
Sp::call_site(DEFAULT_CASING),
);
let tokens = {
let name = attrs.cased_name();
quote!(::clap::App::new(#name))
};
GenOutput { tokens, attrs }
}
pub fn gen_into_app_impl_for_enum(
name: &syn::Ident,
attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
pub fn gen_into_app_impl_for_enum(name: &syn::Ident, attrs: &[syn::Attribute]) -> GenOutput {
let into_app_fn = gen_into_app_fn_for_enum(attrs);
let into_app_fn_tokens = into_app_fn.tokens;
quote! {
let tokens = quote! {
impl ::clap::IntoApp for #name {
#into_app_fn
#into_app_fn_tokens
}
impl<'b> Into<::clap::App<'b>> for #name {
@ -88,16 +105,28 @@ pub fn gen_into_app_impl_for_enum(
<#name as ::clap::IntoApp>::into_app()
}
}
};
GenOutput {
tokens,
attrs: into_app_fn.attrs,
}
}
pub fn gen_into_app_fn_for_enum(enum_attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
pub fn gen_into_app_fn_for_enum(enum_attrs: &[syn::Attribute]) -> GenOutput {
let gen = gen_app_builder(enum_attrs);
quote! {
let app_tokens = gen.tokens;
let tokens = quote! {
fn into_app<'b>() -> ::clap::App<'b> {
let app = #gen
let app = #app_tokens
.setting(::clap::AppSettings::SubcommandRequiredElseHelp);
Self::augment_app(app)
}
};
GenOutput {
tokens,
attrs: gen.attrs,
}
}

View file

@ -15,39 +15,16 @@ use syn;
pub mod arg_enum;
pub mod attrs;
pub mod parse;
pub mod spanned;
pub mod ty;
mod clap;
mod from_argmatches;
mod into_app;
pub use self::arg_enum::derive_arg_enum;
pub use self::attrs::{Attrs, Kind, Parser, Ty};
pub use self::attrs::{Attrs, Kind, Name, Parser, ParserKind, CasingStyle, GenOutput, DEFAULT_CASING};
pub use self::ty::{sub_type, Ty};
pub use self::clap::derive_clap;
pub use self::from_argmatches::derive_from_argmatches;
pub use self::into_app::derive_into_app;
fn sub_type(t: &syn::Type) -> Option<&syn::Type> {
let segs = match *t {
syn::Type::Path(syn::TypePath {
path: syn::Path { ref segments, .. },
..
}) => segments,
_ => return None,
};
match *segs.iter().last().unwrap() {
syn::PathSegment {
arguments:
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
ref args, ..
}),
..
} if args.len() == 1 =>
{
if let syn::GenericArgument::Type(ref ty) = args[0] {
Some(ty)
} else {
None
}
}
_ => None,
}
}

268
src/derives/parse.rs Normal file
View file

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

101
src/derives/spanned.rs Normal file
View file

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

108
src/derives/ty.rs Normal file
View file

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

View file

@ -21,7 +21,11 @@ extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
extern crate heck;
extern crate proc_macro2;
extern crate proc_macro_error;
use proc_macro_error::proc_macro_error;
mod derives;
@ -34,6 +38,7 @@ mod derives;
/// Generates the `Clap` impl.
#[proc_macro_derive(Clap, attributes(clap))]
#[proc_macro_error]
pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
derives::derive_clap(&input).into()
@ -41,6 +46,7 @@ pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// Generates the `IntoApp` impl.
#[proc_macro_derive(IntoApp, attributes(clap))]
#[proc_macro_error]
pub fn into_app(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
derives::derive_into_app(&input).into()
@ -48,6 +54,7 @@ pub fn into_app(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// Generates the `FromArgMatches` impl.
#[proc_macro_derive(FromArgMatches)]
#[proc_macro_error]
pub fn from_argmatches(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: syn::DeriveInput = syn::parse(input).unwrap();
derives::derive_from_argmatches(&input).into()

290
tests/argument_naming.rs Normal file
View file

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

View file

@ -12,9 +12,6 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
#[test]
@ -51,18 +48,6 @@ fn argument_with_default() {
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn argument_with_raw_default() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(raw(default_value = r#""42""#))]
arg: i32,
}
assert_eq!(Opt { arg: 24 }, Opt::parse_from(&["test", "24"]));
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err());
}
#[test]
fn arguments() {
#[derive(Clap, PartialEq, Debug)]

View file

@ -12,51 +12,41 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
mod utils;
use clap::Clap;
use utils::*;
#[test]
fn no_author_version_about() {
use clap::IntoApp;
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "foo", author = "", version = "")]
#[clap(name = "foo", no_version)]
struct Opt {}
let mut output = Vec::new();
Opt::into_app().write_long_help(&mut output).unwrap();
let output = String::from_utf8(output).unwrap();
let output = get_long_help::<Opt>();
assert!(output.starts_with("foo \n\nUSAGE:"));
}
static ENV_HELP: &str = "clap_derive 0.3.0
Guillaume Pinot <texitoi@texitoi.eu>, Kevin K. <kbknapp@gmail.com>, hoverbear <andrew@hoverbear.org>
USAGE:
clap_derive
FLAGS:
-h, --help
Prints help information
-V, --version
Prints version information
";
#[test]
fn use_env() {
use clap::IntoApp;
#[derive(Clap, PartialEq, Debug)]
#[clap()]
#[clap(author, about)]
struct Opt {}
let mut output = Vec::new();
Opt::into_app().write_long_help(&mut output).unwrap();
let output = String::from_utf8(output).unwrap();
assert_eq!(output, ENV_HELP);
let output = get_long_help::<Opt>();
assert!(output.starts_with("clap_derive"));
assert!(output.contains("Guillaume Pinot <texitoi@texitoi.eu>, Kevin K. <kbknapp@gmail.com>"));
assert!(output.contains("Parse command line argument by defining a struct, derive crate"));
}
#[test]
fn explicit_version_not_str() {
const VERSION: &str = "custom version";
#[derive(Clap)]
#[clap(version = VERSION)]
pub struct Opt {}
let output = get_long_help::<Opt>();
assert!(output.contains("custom version"));
}

View file

@ -12,9 +12,6 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
#[test]

View file

@ -12,27 +12,24 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
use std::ffi::OsStr;
use std::ffi::{CString, OsStr, OsString};
use std::num::ParseIntError;
use std::path::PathBuf;
#[derive(Clap, PartialEq, Debug)]
struct PathOpt {
#[clap(short = "p", long = "path", parse(from_os_str))]
#[clap(short, long, parse(from_os_str))]
path: PathBuf,
#[clap(short = "d", default_value = "../", parse(from_os_str))]
#[clap(short, default_value = "../", parse(from_os_str))]
default_path: PathBuf,
#[clap(short = "v", parse(from_os_str))]
#[clap(short, parse(from_os_str))]
vector_path: Vec<PathBuf>,
#[clap(short = "o", parse(from_os_str))]
#[clap(short, parse(from_os_str))]
option_path_1: Option<PathBuf>,
#[clap(short = "q", parse(from_os_str))]
@ -60,11 +57,13 @@ fn test_path_opt_simple() {
);
}
fn parse_hex(input: &str) -> Result<u64, ParseIntError> { u64::from_str_radix(input, 16) }
fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(input, 16)
}
#[derive(Clap, PartialEq, Debug)]
struct HexOpt {
#[clap(short = "n", parse(try_from_str = "parse_hex"))]
#[clap(short, parse(try_from_str = parse_hex))]
number: u64,
}
@ -83,20 +82,28 @@ fn test_parse_hex() {
assert!(err.message.contains("invalid digit found in string"), err);
}
fn custom_parser_1(_: &str) -> &'static str { "A" }
fn custom_parser_2(_: &str) -> Result<&'static str, u32> { Ok("B") }
fn custom_parser_3(_: &OsStr) -> &'static str { "C" }
fn custom_parser_4(_: &OsStr) -> Result<&'static str, String> { Ok("D") }
fn custom_parser_1(_: &str) -> &'static str {
"A"
}
fn custom_parser_2(_: &str) -> Result<&'static str, u32> {
Ok("B")
}
fn custom_parser_3(_: &OsStr) -> &'static str {
"C"
}
fn custom_parser_4(_: &OsStr) -> Result<&'static str, String> {
Ok("D")
}
#[derive(Clap, PartialEq, Debug)]
struct NoOpOpt {
#[clap(short = "a", parse(from_str = "custom_parser_1"))]
#[clap(short, parse(from_str = custom_parser_1))]
a: &'static str,
#[clap(short = "b", parse(try_from_str = "custom_parser_2"))]
#[clap(short, parse(try_from_str = custom_parser_2))]
b: &'static str,
#[clap(short = "c", parse(from_os_str = "custom_parser_3"))]
#[clap(short, parse(from_os_str = custom_parser_3))]
c: &'static str,
#[clap(short = "d", parse(try_from_os_str = "custom_parser_4"))]
#[clap(short, parse(try_from_os_str = custom_parser_4))]
d: &'static str,
}
@ -113,19 +120,19 @@ fn test_every_custom_parser() {
);
}
// Note: can't use `Vec<u8>` directly, as structopt would instead look for
// Note: can't use `Vec<u8>` directly, as clap would instead look for
// conversion function from `&str` to `u8`.
type Bytes = Vec<u8>;
#[derive(Clap, PartialEq, Debug)]
struct DefaultedOpt {
#[clap(short = "b", parse(from_str))]
#[clap(short, parse(from_str))]
bytes: Bytes,
#[clap(short = "i", parse(try_from_str))]
#[clap(short, parse(try_from_str))]
integer: u64,
#[clap(short = "p", parse(from_os_str))]
#[clap(short, parse(from_os_str))]
path: PathBuf,
}
@ -152,27 +159,25 @@ fn test_parser_with_default_value() {
#[derive(PartialEq, Debug)]
struct Foo(u8);
fn foo(value: u64) -> Foo { Foo(value as u8) }
fn foo(value: u64) -> Foo {
Foo(value as u8)
}
#[derive(Clap, PartialEq, Debug)]
struct Occurrences {
#[clap(short = "s", long = "signed", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
signed: i32,
#[clap(short = "l", parse(from_occurrences))]
#[clap(short, parse(from_occurrences))]
little_signed: i8,
#[clap(short = "u", parse(from_occurrences))]
#[clap(short, parse(from_occurrences))]
unsigned: usize,
#[clap(short = "r", parse(from_occurrences))]
little_unsigned: u8,
#[clap(
short = "c",
long = "custom",
parse(from_occurrences = "foo")
)]
#[clap(short, long, parse(from_occurrences = foo))]
custom: Foo,
}
@ -203,17 +208,17 @@ fn test_custom_bool() {
}
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "d", parse(try_from_str = "parse_bool"))]
#[clap(short, parse(try_from_str = parse_bool))]
debug: bool,
#[clap(
short = "v",
short,
default_value = "false",
parse(try_from_str = "parse_bool")
parse(try_from_str = parse_bool)
)]
verbose: bool,
#[clap(short = "t", parse(try_from_str = "parse_bool"))]
#[clap(short, parse(try_from_str = parse_bool))]
tribool: Option<bool>,
#[clap(short = "b", parse(try_from_str = "parse_bool"))]
#[clap(short, parse(try_from_str = parse_bool))]
bitset: Vec<bool>,
}
@ -284,3 +289,21 @@ fn test_custom_bool() {
Opt::parse_from(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"])
);
}
#[test]
fn test_cstring() {
use clap::IntoApp;
#[derive(Clap)]
struct Opt {
#[clap(parse(try_from_str = CString::new))]
c_string: CString,
}
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt::parse_from(&["test", "bla"]).c_string.to_bytes(),
b"bla"
);
assert!(Opt::try_parse_from(&["test", "bla\0bla"]).is_err());
}

View file

@ -13,21 +13,20 @@
// MIT/Apache 2.0 license.
#![deny(warnings)]
#![cfg(feature = "nightly")] // TODO: remove that when never is stable
#![feature(never_type)]
#[macro_use]
extern crate clap;
use clap::Clap;
fn try_str(s: &str) -> Result<String, !> { Ok(s.into()) }
fn try_str(s: &str) -> Result<String, std::convert::Infallible> {
Ok(s.into())
}
#[test]
fn warning_never_struct() {
#[derive(Debug, PartialEq, Clap)]
struct Opt {
#[clap(parse(try_from_str = "try_str"))]
#[clap(parse(try_from_str = try_str))]
s: String,
}
assert_eq!(
@ -43,7 +42,7 @@ fn warning_never_enum() {
#[derive(Debug, PartialEq, Clap)]
enum Opt {
Foo {
#[clap(parse(try_from_str = "try_str"))]
#[clap(parse(try_from_str = try_str))]
s: String,
},
}
@ -51,6 +50,6 @@ fn warning_never_enum() {
Opt::Foo {
s: "foo".to_string()
},
Opt::parse_from(&["test", "Foo", "foo"])
Opt::parse_from(&["test", "foo", "foo"])
);
}

View file

@ -12,71 +12,105 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
mod utils;
use clap::Clap;
use utils::*;
#[test]
fn commets_intead_of_actual_help() {
use clap::IntoApp;
fn doc_comments() {
/// Lorem ipsum
#[derive(Clap, PartialEq, Debug)]
struct LoremIpsum {
/// Fooify a bar
/// and a baz
#[clap(short = "f", long = "foo")]
#[clap(short, long)]
foo: bool,
}
let mut output = Vec::new();
LoremIpsum::into_app().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"));
let help = get_long_help::<LoremIpsum>();
assert!(help.contains("Lorem ipsum"));
assert!(help.contains("Fooify a bar and a baz"));
}
#[test]
fn help_is_better_than_comments() {
use clap::IntoApp;
/// Lorem ipsum
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
/// Fooify a bar
#[clap(
short = "f",
long = "foo",
help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES"
)]
#[clap(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")]
foo: bool,
}
let mut output = Vec::new();
LoremIpsum::into_app().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"));
let help = get_long_help::<LoremIpsum>();
assert!(help.contains("Dolor sit amet"));
assert!(!help.contains("Lorem ipsum"));
assert!(help.contains("DO NOT PASS A BAR"));
}
#[test]
fn empty_line_in_doc_comment_is_double_linefeed() {
use clap::IntoApp;
/// Foo.
///
/// Bar
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", author = "", version = "")]
#[clap(name = "lorem-ipsum", no_version)]
struct LoremIpsum {}
let mut output = Vec::new();
LoremIpsum::into_app().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:"));
let help = get_long_help::<LoremIpsum>();
assert!(help.starts_with("lorem-ipsum \nFoo.\n\nBar\n\nUSAGE:"));
}
#[test]
fn field_long_doc_comment_both_help_long_help() {
/// Lorem ipsumclap
#[derive(Clap, PartialEq, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
/// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES.
///
/// Or something else
#[clap(long)]
foo: bool,
}
let short_help = get_help::<LoremIpsum>();
let long_help = get_long_help::<LoremIpsum>();
assert!(short_help.contains("CIRCUMSTANCES"));
assert!(!short_help.contains("CIRCUMSTANCES."));
assert!(!short_help.contains("Or something else"));
assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES"));
assert!(long_help.contains("Or something else"));
}
#[test]
fn top_long_doc_comment_both_help_long_help() {
/// Lorem ipsumclap
#[derive(Clap, Debug)]
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
#[clap(subcommand)]
foo: SubCommand,
}
#[derive(Clap, Debug)]
pub enum SubCommand {
/// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES
///
/// Or something else
Foo {
#[clap(help = "foo")]
bars: Vec<String>,
},
}
let short_help = get_help::<LoremIpsum>();
let long_help = get_subcommand_long_help::<LoremIpsum>("foo");
assert!(!short_help.contains("Or something else"));
assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES"));
assert!(long_help.contains("Or something else"));
}

View file

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

View file

@ -12,16 +12,13 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
#[test]
fn unique_flag() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", long = "alice")]
#[clap(short, long)]
alice: bool,
}
@ -38,9 +35,9 @@ fn unique_flag() {
fn multiple_flag() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", long = "alice", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
alice: u64,
#[clap(short = "b", long = "bob", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
bob: u8,
}
@ -62,13 +59,44 @@ fn multiple_flag() {
assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err());
}
fn parse_from_flag(b: bool) -> std::sync::atomic::AtomicBool {
std::sync::atomic::AtomicBool::new(b)
}
#[test]
fn non_bool_flags() {
#[derive(Clap, Debug)]
struct Opt {
#[clap(short, long, parse(from_flag = parse_from_flag))]
alice: std::sync::atomic::AtomicBool,
#[clap(short, long, parse(from_flag))]
bob: std::sync::atomic::AtomicBool,
}
let falsey = Opt::parse_from(&["test"]);
assert!(!falsey.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(!falsey.bob.load(std::sync::atomic::Ordering::Relaxed));
let alice = Opt::parse_from(&["test", "-a"]);
assert!(alice.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(!alice.bob.load(std::sync::atomic::Ordering::Relaxed));
let bob = Opt::parse_from(&["test", "-b"]);
assert!(!bob.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(bob.bob.load(std::sync::atomic::Ordering::Relaxed));
let both = Opt::parse_from(&["test", "-b", "-a"]);
assert!(both.alice.load(std::sync::atomic::Ordering::Relaxed));
assert!(both.bob.load(std::sync::atomic::Ordering::Relaxed));
}
#[test]
fn combined_flags() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", long = "alice")]
#[clap(short, long)]
alice: bool,
#[clap(short = "b", long = "bob", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
bob: u64,
}

View file

@ -12,9 +12,6 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
#[test]
@ -67,7 +64,7 @@ fn flatten_in_subcommand() {
#[derive(Clap, PartialEq, Debug)]
struct Add {
#[clap(short = "i")]
#[clap(short)]
interactive: bool,
#[clap(flatten)]
common: Common,
@ -75,15 +72,13 @@ fn flatten_in_subcommand() {
#[derive(Clap, PartialEq, Debug)]
enum Opt {
#[clap(name = "fetch")]
Fetch {
#[clap(short = "a")]
#[clap(short)]
all: bool,
#[clap(flatten)]
common: Common,
},
#[clap(name = "add")]
Add(Add),
}

50
tests/issues.rs Normal file
View file

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

13
tests/macro-errors.rs Normal file
View file

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

View file

@ -12,16 +12,13 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "f", long = "force")]
#[clap(short, long)]
force: bool,
#[clap(short = "v", long = "verbose", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
verbose: u64,
#[clap(subcommand)]
cmd: Sub,
@ -29,17 +26,15 @@ struct Opt {
#[derive(Clap, PartialEq, Debug)]
enum Sub {
#[clap(name = "fetch")]
Fetch {},
#[clap(name = "add")]
Add {},
}
#[derive(Clap, PartialEq, Debug)]
struct Opt2 {
#[clap(short = "f", long = "force")]
#[clap(short, long)]
force: bool,
#[clap(short = "v", long = "verbose", parse(from_occurrences))]
#[clap(short, long, parse(from_occurrences))]
verbose: u64,
#[clap(subcommand)]
cmd: Option<Sub>,
@ -114,7 +109,7 @@ fn test_badinput() {
#[derive(Clap, PartialEq, Debug)]
struct Opt3 {
#[clap(short = "a", long = "all")]
#[clap(short, long)]
all: bool,
#[clap(subcommand)]
cmd: Sub2,
@ -122,21 +117,17 @@ struct Opt3 {
#[derive(Clap, PartialEq, Debug)]
enum Sub2 {
#[clap(name = "foo")]
Foo {
file: String,
#[clap(subcommand)]
cmd: Sub3,
},
#[clap(name = "bar")]
Bar {},
}
#[derive(Clap, PartialEq, Debug)]
enum Sub3 {
#[clap(name = "baz")]
Baz {},
#[clap(name = "quux")]
Quux {},
}
@ -156,12 +147,10 @@ fn test_subsubcommand() {
#[derive(Clap, PartialEq, Debug)]
enum SubSubCmdWithOption {
#[clap(name = "remote")]
Remote {
#[clap(subcommand)]
cmd: Option<Remote>,
},
#[clap(name = "stash")]
Stash {
#[clap(subcommand)]
cmd: Stash,
@ -169,17 +158,13 @@ enum SubSubCmdWithOption {
}
#[derive(Clap, PartialEq, Debug)]
enum Remote {
#[clap(name = "add")]
Add { name: String, url: String },
#[clap(name = "remove")]
Remove { name: String },
}
#[derive(Clap, PartialEq, Debug)]
enum Stash {
#[clap(name = "save")]
Save,
#[clap(name = "pop")]
Pop,
}
@ -188,10 +173,7 @@ fn sub_sub_cmd_with_option() {
fn make(args: &[&str]) -> Option<SubSubCmdWithOption> {
use clap::{FromArgMatches, IntoApp};
SubSubCmdWithOption::into_app()
.try_get_matches_from(args)
.ok()
.map(|m| SubSubCmdWithOption::from_argmatches(&m))
SubSubCmdWithOption::try_parse_from(args).ok()
}
assert_eq!(
Some(SubSubCmdWithOption::Remote { cmd: None }),

View file

@ -12,38 +12,36 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::{AppSettings, Clap};
use std::num::ParseIntError;
pub const DISPLAY_ORDER: usize = 2;
// Check if the global settings compile
#[derive(Clap, Debug, PartialEq, Eq)]
#[clap(raw(global_setting = "AppSettings::ColoredHelp"))]
#[clap(global_setting = AppSettings::ColoredHelp)]
struct Opt {
#[clap(
long = "x",
raw(
display_order = "2",
next_line_help = "true",
default_value = r#""0""#,
require_equals = "true"
)
display_order = DISPLAY_ORDER,
next_line_help = true,
default_value = "0",
require_equals = true
)]
x: i32,
#[clap(short = "l", long = "level", raw(aliases = r#"&["set-level", "lvl"]"#))]
#[clap(short = "l", long = "level", aliases = &["set-level", "lvl"])]
level: String,
#[clap(long = "values")]
#[clap(long("values"))]
values: Vec<i32>,
#[clap(name = "FILE", raw(requires_if = r#""FILE", "values""#))]
#[clap(name = "FILE", requires_if("FILE", "values"))]
files: Vec<String>,
}
#[test]
fn test_raw_slice() {
fn test_slice() {
assert_eq!(
Opt {
x: 0,
@ -83,7 +81,7 @@ fn test_raw_slice() {
}
#[test]
fn test_raw_multi_args() {
fn test_multi_args() {
assert_eq!(
Opt {
x: 0,
@ -105,13 +103,13 @@ fn test_raw_multi_args() {
}
#[test]
fn test_raw_multi_args_fail() {
fn test_multi_args_fail() {
let result = Opt::try_parse_from(&["test", "-l", "1", "--", "FILE"]);
assert!(result.is_err());
}
#[test]
fn test_raw_bool() {
fn test_bool() {
assert_eq!(
Opt {
x: 1,
@ -124,3 +122,28 @@ fn test_raw_bool() {
let result = Opt::try_parse_from(&["test", "-l", "1", "--x", "1"]);
assert!(result.is_err());
}
fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(input, 16)
}
#[derive(Clap, PartialEq, Debug)]
struct HexOpt {
#[clap(short = "n", parse(try_from_str = parse_hex))]
number: u64,
}
#[test]
fn test_parse_hex_function_path() {
assert_eq!(
HexOpt { number: 5 },
HexOpt::parse_from(&["test", "-n", "5"])
);
assert_eq!(
HexOpt { number: 0xabcdef },
HexOpt::parse_from(&["test", "-n", "abcdef"])
);
let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err();
assert!(err.message.contains("invalid digit found in string"), err);
}

View file

@ -12,16 +12,13 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
#[test]
fn required_option() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", long = "arg")]
#[clap(short, long)]
arg: i32,
}
assert_eq!(Opt { arg: 42 }, Opt::parse_from(&["test", "-a42"]));
@ -35,7 +32,7 @@ fn required_option() {
fn optional_option() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a")]
#[clap(short)]
arg: Option<i32>,
}
assert_eq!(Opt { arg: Some(42) }, Opt::parse_from(&["test", "-a42"]));
@ -47,7 +44,7 @@ fn optional_option() {
fn option_with_default() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", default_value = "42")]
#[clap(short, default_value = "42")]
arg: i32,
}
assert_eq!(Opt { arg: 24 }, Opt::parse_from(&["test", "-a24"]));
@ -59,7 +56,7 @@ fn option_with_default() {
fn option_with_raw_default() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", raw(default_value = r#""42""#))]
#[clap(short, default_value = "42")]
arg: i32,
}
assert_eq!(Opt { arg: 24 }, Opt::parse_from(&["test", "-a24"]));
@ -71,7 +68,7 @@ fn option_with_raw_default() {
fn options() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", long = "arg")]
#[clap(short, long)]
arg: Vec<i32>,
}
assert_eq!(Opt { arg: vec![24] }, Opt::parse_from(&["test", "-a24"]));
@ -86,7 +83,7 @@ fn options() {
fn default_value() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short = "a", default_value = "test")]
#[clap(short, default_value = "test")]
arg: String,
}
assert_eq!(Opt { arg: "test".into() }, Opt::parse_from(&["test"]));
@ -95,3 +92,194 @@ fn default_value() {
Opt::parse_from(&["test", "-afoo"])
);
}
#[test]
fn option_from_str() {
#[derive(Debug, PartialEq)]
struct A;
impl<'a> From<&'a str> for A {
fn from(_: &str) -> A {
A
}
}
#[derive(Debug, Clap, PartialEq)]
struct Opt {
#[clap(parse(from_str))]
a: Option<A>,
}
assert_eq!(Opt { a: None }, Opt::parse_from(&["test"]));
assert_eq!(Opt { a: Some(A) }, Opt::parse_from(&["test", "foo"]));
}
#[test]
fn optional_argument_for_optional_option() {
use clap::IntoApp;
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
#[allow(clippy::option_option)]
arg: Option<Option<i32>>,
}
assert_eq!(
Opt {
arg: Some(Some(42))
},
Opt::parse_from(&["test", "-a42"])
);
assert_eq!(Opt { arg: Some(None) }, Opt::parse_from(&["test", "-a"]));
assert_eq!(Opt { arg: None }, Opt::parse_from(&["test"]));
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
}
#[test]
fn two_option_options() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Option<i32>>,
#[clap(long)]
field: Option<Option<String>>,
}
assert_eq!(
Opt {
arg: Some(Some(42)),
field: Some(Some("f".into()))
},
Opt::parse_from(&["test", "-a42", "--field", "f"])
);
assert_eq!(
Opt {
arg: Some(Some(42)),
field: Some(None)
},
Opt::parse_from(&["test", "-a42", "--field"])
);
assert_eq!(
Opt {
arg: Some(None),
field: Some(None)
},
Opt::parse_from(&["test", "-a", "--field"])
);
assert_eq!(
Opt {
arg: Some(None),
field: Some(Some("f".into()))
},
Opt::parse_from(&["test", "-a", "--field", "f"])
);
assert_eq!(
Opt {
arg: None,
field: Some(None)
},
Opt::parse_from(&["test", "--field"])
);
assert_eq!(
Opt {
arg: None,
field: None
},
Opt::parse_from(&["test"])
);
}
#[test]
fn optional_vec() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Vec<i32>>,
}
assert_eq!(
Opt { arg: Some(vec![1]) },
Opt::parse_from(&["test", "-a", "1"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a2"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a2", "-a"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a", "-a2"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a", "1", "2"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2, 3])
},
Opt::parse_from(&["test", "-a", "1", "2", "-a", "3"])
);
assert_eq!(Opt { arg: Some(vec![]) }, Opt::parse_from(&["test", "-a"]));
assert_eq!(
Opt { arg: Some(vec![]) },
Opt::parse_from(&["test", "-a", "-a"])
);
assert_eq!(Opt { arg: None }, Opt::parse_from(&["test"]));
}
#[test]
fn two_optional_vecs() {
#[derive(Clap, PartialEq, Debug)]
struct Opt {
#[clap(short)]
arg: Option<Vec<i32>>,
#[clap(short)]
b: Option<Vec<i32>>,
}
assert_eq!(
Opt {
arg: Some(vec![1]),
b: Some(vec![])
},
Opt::parse_from(&["test", "-a", "1", "-b"])
);
assert_eq!(
Opt {
arg: Some(vec![1]),
b: Some(vec![])
},
Opt::parse_from(&["test", "-a", "-b", "-a1"])
);
assert_eq!(
Opt {
arg: Some(vec![1, 2]),
b: Some(vec![1, 2])
},
Opt::parse_from(&["test", "-a1", "-a2", "-b1", "-b2"])
);
assert_eq!(Opt { arg: None, b: None }, Opt::parse_from(&["test"]));
}

View file

@ -12,25 +12,24 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
use clap::Clap;
mod options {
use clap::Clap;
#[derive(Debug, Clap)]
pub struct Options {
#[clap(subcommand)]
pub subcommand: ::subcommands::SubCommand,
pub subcommand: super::subcommands::SubCommand,
}
}
mod subcommands {
use clap::Clap;
#[derive(Debug, Clap)]
pub enum SubCommand {
#[clap(name = "foo", about = "foo")]
/// foo
Foo {
#[clap(help = "foo")]
/// foo
bars: Vec<String>,
},
}

29
tests/raw_bool_literal.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.
use clap::Clap;
#[test]
fn raw_bool_literal() {
#[derive(Clap, Debug, PartialEq)]
#[clap(no_version, name = "raw_bool")]
struct Opt {
#[clap(raw(false))]
a: String,
#[clap(raw(true))]
b: String,
}
assert_eq!(
Opt {
a: "one".into(),
b: "--help".into()
},
Opt::parse_from(&["test", "one", "--", "--help"])
);
}

17
tests/raw_idents.rs Normal file
View file

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

148
tests/skip.rs Normal file
View file

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

73
tests/special_types.rs Normal file
View file

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

View file

@ -12,28 +12,27 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#[macro_use]
extern crate clap;
mod utils;
use clap::Clap;
use utils::*;
#[derive(Clap, PartialEq, Debug)]
enum Opt {
#[clap(name = "fetch", about = "Fetch stuff from GitHub.")]
/// Fetch stuff from GitHub
Fetch {
#[clap(long = "all")]
#[clap(long)]
all: bool,
#[clap(short = "f", long = "force")]
#[clap(short, long)]
/// Overwrite local branches.
force: bool,
repo: String,
},
#[clap(name = "add")]
Add {
#[clap(short = "i", long = "interactive")]
#[clap(short, long)]
interactive: bool,
#[clap(short = "v", long = "verbose")]
#[clap(short, long)]
verbose: bool,
},
}
@ -90,7 +89,6 @@ fn test_no_parse() {
#[derive(Clap, PartialEq, Debug)]
enum Opt2 {
#[clap(name = "do-something")]
DoSomething { arg: String },
}
@ -108,11 +106,8 @@ fn test_hyphenated_subcommands() {
#[derive(Clap, PartialEq, Debug)]
enum Opt3 {
#[clap(name = "add")]
Add,
#[clap(name = "init")]
Init,
#[clap(name = "fetch")]
Fetch,
}
@ -135,13 +130,11 @@ struct Fetch {
}
#[derive(Clap, PartialEq, Debug)]
enum Opt4 {
/// Not shown
#[clap(name = "add", about = "Add a file")]
// Not shown
/// Add a file
Add(Add),
#[clap(name = "init")]
Init,
/// download history from remote
#[clap(name = "fetch")]
Fetch(Fetch),
}
@ -163,9 +156,7 @@ fn test_tuple_commands() {
Opt4::parse_from(&["test", "fetch", "origin"])
);
let mut output = Vec::new();
Opt4::into_app().write_long_help(&mut output).unwrap();
let output = String::from_utf8(output).unwrap();
let output = get_long_help::<Opt4>();
assert!(output.contains("download history from remote"));
assert!(output.contains("Add a file"));
@ -176,15 +167,12 @@ fn test_tuple_commands() {
fn enum_in_enum_subsubcommand() {
#[derive(Clap, Debug, PartialEq)]
pub enum Opt {
#[clap(name = "daemon")]
Daemon(DaemonCommand),
}
#[derive(Clap, Debug, PartialEq)]
pub enum DaemonCommand {
#[clap(name = "start")]
Start,
#[clap(name = "stop")]
Stop,
}
@ -213,7 +201,7 @@ fn flatten_enum() {
assert!(Opt::try_parse_from(&["test"]).is_err());
assert_eq!(
Opt::parse_from(&["test", "Foo"]),
Opt::parse_from(&["test", "foo"]),
Opt {
sub_cmd: SubCmd::Foo
}

View file

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

View file

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

21
tests/ui/bool_required.rs Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
use clap::Clap;
#[derive(Clap, Debug)]
struct Opt {
verbose: bool,
}
fn main() {
Opt::parse();
}

View file

@ -0,0 +1,10 @@
error: `bool` cannot be used as positional parameter with default parser
= help: if you want to create a flag add `long` or `short`
= help: If you really want a boolean parameter add an explicit parser, for example `parse(try_from_str)`
= note: see also https://github.com/clap-rs/clap_derive/tree/master/examples/true_or_false.rs
--> $DIR/positional_bool.rs:5:14
|
5 | verbose: bool,
| ^^^^

20
tests/ui/raw.rs Normal file
View file

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

9
tests/ui/raw.stderr Normal file
View file

@ -0,0 +1,9 @@
error: `#[clap(raw(...))` attributes are removed, they are replaced with raw methods
= help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)`
= note: if you need to call `clap::Arg/App::case_insensitive` method you can do it like this: #[clap(case_insensitive(true))]
--> $DIR/raw.rs:13:12
|
13 | #[clap(raw(case_insensitive = "true"))]
| ^^^

View file

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

View file

@ -0,0 +1,5 @@
error: unsupported casing: `fail`
--> $DIR/rename_all_wrong_casing.rs:12:37
|
12 | #[clap(name = "basic", rename_all = "fail")]
| ^^^^^^

42
tests/ui/skip_flatten.rs Normal file
View file

@ -0,0 +1,42 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "make-cookie")]
struct MakeCookie {
#[clap(short)]
s: String,
#[clap(skip, flatten)]
cmd: Command,
}
#[derive(Clap, Debug)]
enum Command {
#[clap(name = "pound")]
/// Pound acorns into flour for cookie dough.
Pound { acorns: u32 },
Sparkle {
#[clap(short)]
color: String,
},
}
impl Default for Command {
fn default() -> Self {
Command::Pound { acorns: 0 }
}
}
fn main() {
let opt = MakeCookie::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: subcommand, flatten and skip cannot be used together
--> $DIR/skip_flatten.rs:17:18
|
17 | #[clap(skip, flatten)]
| ^^^^^^^

View file

@ -0,0 +1,42 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "make-cookie")]
struct MakeCookie {
#[clap(short)]
s: String,
#[clap(subcommand, skip)]
cmd: Command,
}
#[derive(Clap, Debug)]
enum Command {
#[clap(name = "pound")]
/// Pound acorns into flour for cookie dough.
Pound { acorns: u32 },
Sparkle {
#[clap(short)]
color: String,
},
}
impl Default for Command {
fn default() -> Self {
Command::Pound { acorns: 0 }
}
}
fn main() {
let opt = MakeCookie::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: subcommand, flatten and skip cannot be used together
--> $DIR/skip_subcommand.rs:17:24
|
17 | #[clap(subcommand, skip)]
| ^^^^

View file

@ -0,0 +1,15 @@
use clap::Clap;
#[derive(Clap, Debug)]
#[clap(name = "test")]
pub struct Opt {
#[clap(long)]
a: u32,
#[clap(skip, long)]
b: u32,
}
fn main() {
let opt = Opt::parse();
println!("{:?}", opt);
}

View file

@ -0,0 +1,5 @@
error: methods are not allowed for skipped fields
--> $DIR/skip_with_other_options.rs:8:12
|
8 | #[clap(skip, long)]
| ^^^^

View file

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

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