mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 14:54:15 +00:00
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:
parent
2622165be3
commit
0352bb30c8
120 changed files with 4074 additions and 925 deletions
14
Cargo.toml
14
Cargo.toml
|
@ -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 = []
|
||||
|
|
68
README.md
68
README.md
|
@ -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
78
examples/README.md
Normal 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
19
examples/after_help.rs
Normal 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);
|
||||
}
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
74
examples/doc_comments.rs
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
// }
|
||||
|
|
|
@ -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
26
examples/env.rs
Normal 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);
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
},
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
15
examples/negative_flag.rs
Normal 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);
|
||||
}
|
|
@ -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 {}
|
||||
|
||||
|
|
|
@ -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
74
examples/rename_all.rs
Normal 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);
|
||||
}
|
|
@ -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
47
examples/skip.rs
Normal 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")
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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
41
examples/true_or_false.rs
Normal 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());
|
||||
}
|
|
@ -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)?;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 )
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
268
src/derives/parse.rs
Normal 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
101
src/derives/spanned.rs
Normal 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
108
src/derives/ty.rs
Normal 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())
|
||||
}
|
|
@ -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
290
tests/argument_naming.rs
Normal 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"])
|
||||
);
|
||||
}
|
|
@ -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)]
|
||||
|
|
|
@ -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"));
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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"])
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
32
tests/explicit_name_no_renaming.rs
Normal file
32
tests/explicit_name_no_renaming.rs
Normal 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]..."))
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
50
tests/issues.rs
Normal 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
13
tests/macro-errors.rs
Normal 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");
|
||||
}
|
|
@ -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 }),
|
||||
|
|
|
@ -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);
|
||||
}
|
206
tests/options.rs
206
tests/options.rs
|
@ -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"]));
|
||||
}
|
||||
|
|
|
@ -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
29
tests/raw_bool_literal.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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
17
tests/raw_idents.rs
Normal 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
148
tests/skip.rs
Normal 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
73
tests/special_types.rs
Normal 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"])
|
||||
);
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
21
tests/ui/bool_default_value.rs
Normal file
21
tests/ui/bool_default_value.rs
Normal 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);
|
||||
}
|
5
tests/ui/bool_default_value.stderr
Normal file
5
tests/ui/bool_default_value.stderr
Normal 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
21
tests/ui/bool_required.rs
Normal 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);
|
||||
}
|
5
tests/ui/bool_required.stderr
Normal file
5
tests/ui/bool_required.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: required is meaningless for bool
|
||||
--> $DIR/bool_required.rs:14:19
|
||||
|
|
||||
14 | #[clap(short, required = true)]
|
||||
| ^^^^^^^^
|
22
tests/ui/clap_derive_empty_attr.rs
Normal file
22
tests/ui/clap_derive_empty_attr.rs
Normal 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);
|
||||
}
|
||||
|
5
tests/ui/clap_derive_empty_attr.stderr
Normal file
5
tests/ui/clap_derive_empty_attr.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: expected parentheses after `clap`
|
||||
--> $DIR/clap_derive_empty_attr.rs:14:7
|
||||
|
|
||||
14 | #[clap]
|
||||
| ^^^^
|
22
tests/ui/clap_derive_name_value_attr.rs
Normal file
22
tests/ui/clap_derive_name_value_attr.rs
Normal 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);
|
||||
}
|
||||
|
5
tests/ui/clap_derive_name_value_attr.stderr
Normal file
5
tests/ui/clap_derive_name_value_attr.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: expected parentheses
|
||||
--> $DIR/clap_derive_name_value_attr.rs:14:12
|
||||
|
|
||||
14 | #[clap = "short"]
|
||||
| ^
|
30
tests/ui/flatten_and_doc.rs
Normal file
30
tests/ui/flatten_and_doc.rs
Normal 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);
|
||||
}
|
5
tests/ui/flatten_and_doc.stderr
Normal file
5
tests/ui/flatten_and_doc.stderr
Normal 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)]
|
||||
| ^^^^^^^
|
29
tests/ui/flatten_and_methods.rs
Normal file
29
tests/ui/flatten_and_methods.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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);
|
||||
}
|
5
tests/ui/flatten_and_methods.stderr
Normal file
5
tests/ui/flatten_and_methods.stderr
Normal 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)]
|
||||
| ^^^^^^^
|
29
tests/ui/flatten_and_parse.rs
Normal file
29
tests/ui/flatten_and_parse.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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);
|
||||
}
|
5
tests/ui/flatten_and_parse.stderr
Normal file
5
tests/ui/flatten_and_parse.stderr
Normal 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))]
|
||||
| ^^^^^
|
21
tests/ui/non_existent_attr.rs
Normal file
21
tests/ui/non_existent_attr.rs
Normal 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);
|
||||
}
|
5
tests/ui/non_existent_attr.stderr
Normal file
5
tests/ui/non_existent_attr.stderr
Normal 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<'_>`
|
20
tests/ui/opt_opt_nonpositional.rs
Normal file
20
tests/ui/opt_opt_nonpositional.rs
Normal 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);
|
||||
}
|
5
tests/ui/opt_opt_nonpositional.stderr
Normal file
5
tests/ui/opt_opt_nonpositional.stderr
Normal 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>>,
|
||||
| ^^^^^^
|
20
tests/ui/opt_vec_nonpositional.rs
Normal file
20
tests/ui/opt_vec_nonpositional.rs
Normal 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);
|
||||
}
|
5
tests/ui/opt_vec_nonpositional.stderr
Normal file
5
tests/ui/opt_vec_nonpositional.stderr
Normal 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>>,
|
||||
| ^^^^^^
|
21
tests/ui/option_default_value.rs
Normal file
21
tests/ui/option_default_value.rs
Normal 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);
|
||||
}
|
5
tests/ui/option_default_value.stderr
Normal file
5
tests/ui/option_default_value.stderr
Normal 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)]
|
||||
| ^^^^^^^^^^^^^
|
21
tests/ui/option_required.rs
Normal file
21
tests/ui/option_required.rs
Normal 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);
|
||||
}
|
5
tests/ui/option_required.stderr
Normal file
5
tests/ui/option_required.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: required is meaningless for Option
|
||||
--> $DIR/option_required.rs:14:19
|
||||
|
|
||||
14 | #[clap(short, required = true)]
|
||||
| ^^^^^^^^
|
21
tests/ui/parse_empty_try_from_os.rs
Normal file
21
tests/ui/parse_empty_try_from_os.rs
Normal 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);
|
||||
}
|
5
tests/ui/parse_empty_try_from_os.stderr
Normal file
5
tests/ui/parse_empty_try_from_os.stderr
Normal 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))]
|
||||
| ^^^^^^^^^^^^^^^
|
21
tests/ui/parse_function_is_not_path.rs
Normal file
21
tests/ui/parse_function_is_not_path.rs
Normal 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);
|
||||
}
|
5
tests/ui/parse_function_is_not_path.stderr
Normal file
5
tests/ui/parse_function_is_not_path.stderr
Normal 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"))]
|
||||
| ^^^
|
21
tests/ui/parse_literal_spec.rs
Normal file
21
tests/ui/parse_literal_spec.rs
Normal 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);
|
||||
}
|
5
tests/ui/parse_literal_spec.stderr
Normal file
5
tests/ui/parse_literal_spec.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: parser specification must start with identifier
|
||||
--> $DIR/parse_literal_spec.rs:14:18
|
||||
|
|
||||
14 | #[clap(parse("from_str"))]
|
||||
| ^^^^^^^^^^
|
21
tests/ui/parse_not_zero_args.rs
Normal file
21
tests/ui/parse_not_zero_args.rs
Normal 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);
|
||||
}
|
5
tests/ui/parse_not_zero_args.stderr
Normal file
5
tests/ui/parse_not_zero_args.stderr
Normal 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))]
|
||||
| ^^^^^
|
10
tests/ui/positional_bool.rs
Normal file
10
tests/ui/positional_bool.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use clap::Clap;
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
struct Opt {
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Opt::parse();
|
||||
}
|
10
tests/ui/positional_bool.stderr
Normal file
10
tests/ui/positional_bool.stderr
Normal 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
20
tests/ui/raw.rs
Normal 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
9
tests/ui/raw.stderr
Normal 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"))]
|
||||
| ^^^
|
21
tests/ui/rename_all_wrong_casing.rs
Normal file
21
tests/ui/rename_all_wrong_casing.rs
Normal 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);
|
||||
}
|
5
tests/ui/rename_all_wrong_casing.stderr
Normal file
5
tests/ui/rename_all_wrong_casing.stderr
Normal 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
42
tests/ui/skip_flatten.rs
Normal 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);
|
||||
}
|
5
tests/ui/skip_flatten.stderr
Normal file
5
tests/ui/skip_flatten.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: subcommand, flatten and skip cannot be used together
|
||||
--> $DIR/skip_flatten.rs:17:18
|
||||
|
|
||||
17 | #[clap(skip, flatten)]
|
||||
| ^^^^^^^
|
42
tests/ui/skip_subcommand.rs
Normal file
42
tests/ui/skip_subcommand.rs
Normal 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);
|
||||
}
|
5
tests/ui/skip_subcommand.stderr
Normal file
5
tests/ui/skip_subcommand.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: subcommand, flatten and skip cannot be used together
|
||||
--> $DIR/skip_subcommand.rs:17:24
|
||||
|
|
||||
17 | #[clap(subcommand, skip)]
|
||||
| ^^^^
|
15
tests/ui/skip_with_other_options.rs
Normal file
15
tests/ui/skip_with_other_options.rs
Normal 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);
|
||||
}
|
5
tests/ui/skip_with_other_options.stderr
Normal file
5
tests/ui/skip_with_other_options.stderr
Normal 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)]
|
||||
| ^^^^
|
29
tests/ui/skip_without_default.rs
Normal file
29
tests/ui/skip_without_default.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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
Loading…
Reference in a new issue