From 8f262e896c894e7166f932c5cb6f4d439627783d Mon Sep 17 00:00:00 2001 From: Ivan Veselov Date: Wed, 3 Jul 2019 20:17:10 +0100 Subject: [PATCH 1/2] Support skipping struct fields --- CHANGELOG.md | 3 + examples/example.rs | 5 ++ src/lib.rs | 11 +++ structopt-derive/src/attrs.rs | 11 +++ structopt-derive/src/lib.rs | 3 +- structopt-derive/src/parse.rs | 2 + tests/skip.rs | 94 +++++++++++++++++++++++++ tests/ui/skip_with_other_options.rs | 15 ++++ tests/ui/skip_with_other_options.stderr | 7 ++ tests/ui/skip_without_default.rs | 29 ++++++++ tests/ui/skip_without_default.stderr | 9 +++ 11 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tests/skip.rs create mode 100644 tests/ui/skip_with_other_options.rs create mode 100644 tests/ui/skip_with_other_options.stderr create mode 100644 tests/ui/skip_without_default.rs create mode 100644 tests/ui/skip_without_default.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e92dc64..716c2848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ ## improvements +* Support skipping struct fields by [@sphynx](https://github.com/sphynx) + ([#174](https://github.com/TeXitoi/structopt/issues/174)) + * Add optional feature to support `paw` by [@gameldar](https://github.com/gameldar) ([#187](https://github.com/TeXitoi/structopt/issues/187)) diff --git a/examples/example.rs b/examples/example.rs index 78fb2838..ba6fb417 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -46,6 +46,11 @@ struct Opt { /// argument list is provided (e.g. `--optv a b c`). #[structopt(long = "optv")] optv: Option>, + + /// Skipped option: it won't be parsed and will be filled with the + /// default value for its type (in this case ''). + #[structopt(skip)] + skipped: String, } fn main() { diff --git a/src/lib.rs b/src/lib.rs index c59e58fd..aba16f0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,6 +179,11 @@ //! If an argument is renamed using `name = $NAME` any following call to //! `short` or `long` will use the new name. //! +//! If you want to omit a struct field from the parsing process +//! altogether and just use a default value for it, you can annotate +//! the field with `#[structopt(skip)]`. Note that the field type has +//! to implement `std::default::Default` then. +//! //! **Attention**: If these arguments are used without an explicit name //! the resulting flag is going to be renamed using `kebab-case` if the //! `rename_all` attribute was not specified previously. The same is true @@ -212,6 +217,12 @@ //! /// This option is positional, meaning it is the first unadorned string //! /// you provide (multiple others could follow). //! my_positional: String, +//! +//! /// This option is skipped and will be filled with the default value +//! /// for its type (in this case 0). +//! #[structopt(skip)] +//! skipped: u32, +//! //! } //! //! # fn main() { diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index 0eb15614..92f95738 100755 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -23,6 +23,7 @@ pub enum Kind { Arg(Ty), Subcommand(Ty), FlattenStruct, + Skip, } #[derive(Copy, Clone, PartialEq, Debug)] @@ -179,6 +180,10 @@ impl Attrs { self.set_kind(Kind::FlattenStruct); } + Skip => { + self.set_kind(Kind::Skip); + } + NameLitStr(name, lit) => { self.push_str_method(&name.to_string(), &lit.value()); } @@ -346,6 +351,7 @@ impl Attrs { match res.kind { Kind::Subcommand(_) => panic!("subcommand is only allowed on fields"), Kind::FlattenStruct => panic!("flatten is only allowed on fields"), + Kind::Skip => panic!("skip is only allowed on fields"), Kind::Arg(_) => res, } } @@ -405,6 +411,11 @@ impl Attrs { res.kind = Kind::Subcommand(ty); } + Kind::Skip => { + if !res.methods.iter().all(|m| m.name == "help") { + panic!("methods are not allowed for skipped fields"); + } + } Kind::Arg(_) => { let mut ty = Self::ty_from_field(&field.ty); if res.has_custom_parser { diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index b4a9b0ca..e02359e8 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -87,7 +87,7 @@ fn gen_augmentation( let args = fields.iter().filter_map(|field| { let attrs = Attrs::from_field(field, parent_attribute.casing()); match attrs.kind() { - Kind::Subcommand(_) => None, + Kind::Subcommand(_) | Kind::Skip => None, Kind::FlattenStruct => { let ty = &field.ty; Some(quote! { @@ -183,6 +183,7 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper) } Kind::FlattenStruct => quote!(#field_name: ::structopt::StructOpt::from_clap(matches)), + Kind::Skip => quote!(#field_name: Default::default()), Kind::Arg(ty) => { use crate::Parser::*; let (value_of, values_of, parse) = match *attrs.parser() { diff --git a/structopt-derive/src/parse.rs b/structopt-derive/src/parse.rs index 332713d6..9afeb4e7 100755 --- a/structopt-derive/src/parse.rs +++ b/structopt-derive/src/parse.rs @@ -23,6 +23,7 @@ pub enum StructOptAttr { Long, Flatten, Subcommand, + Skip, Parse(ParserSpec), RenameAll(LitStr), NameLitStr(Ident, LitStr), @@ -94,6 +95,7 @@ impl Parse for StructOptAttr { "short" => Ok(Short), "flatten" => Ok(Flatten), "subcommand" => Ok(Subcommand), + "skip" => Ok(Skip), _ => { let msg = format!("unexpected attribute: {}", name_str); Err(input.error(&msg)) diff --git a/tests/skip.rs b/tests/skip.rs new file mode 100644 index 00000000..d1a0cb85 --- /dev/null +++ b/tests/skip.rs @@ -0,0 +1,94 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn skip_1() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short)] + x: u32, + #[structopt(skip)] + s: u32, + } + + assert!(Opt::from_iter_safe(&["test", "-x", "10", "20"]).is_err()); + assert_eq!( + Opt::from_iter(&["test", "-x", "10"]), + Opt { + x: 10, + s: 0, // default + } + ); +} + +#[test] +fn skip_2() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short)] + x: u32, + #[structopt(skip)] + ss: String, + #[structopt(skip)] + sn: u8, + y: u32, + #[structopt(skip)] + sz: u16, + t: u32, + } + + assert_eq!( + Opt::from_iter(&["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(StructOpt, Debug, PartialEq)] + #[structopt(name = "a")] + pub struct Opt { + #[structopt(long, short)] + number: u32, + #[structopt(skip)] + k: Kind, + #[structopt(skip)] + v: Vec, + } + + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + number: 10, + k: Kind::B, + v: vec![], + } + ); +} diff --git a/tests/ui/skip_with_other_options.rs b/tests/ui/skip_with_other_options.rs new file mode 100644 index 00000000..464bfd22 --- /dev/null +++ b/tests/ui/skip_with_other_options.rs @@ -0,0 +1,15 @@ +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "test")] +pub struct Opts { + #[structopt(long)] + a: u32, + #[structopt(skip, long)] + b: u32, +} + +fn main() { + let opts = Opts::from_args(); + println!("{:?}", opts); +} diff --git a/tests/ui/skip_with_other_options.stderr b/tests/ui/skip_with_other_options.stderr new file mode 100644 index 00000000..5744e5c7 --- /dev/null +++ b/tests/ui/skip_with_other_options.stderr @@ -0,0 +1,7 @@ +error: proc-macro derive panicked + --> $DIR/skip_with_other_options.rs:3:10 + | +3 | #[derive(StructOpt, Debug)] + | ^^^^^^^^^ + | + = help: message: methods are not allowed for skipped fields diff --git a/tests/ui/skip_without_default.rs b/tests/ui/skip_without_default.rs new file mode 100644 index 00000000..44ab5a52 --- /dev/null +++ b/tests/ui/skip_without_default.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(Debug)] +enum Kind { + A, + B, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "test")] +pub struct Opts { + #[structopt(short)] + number: u32, + #[structopt(skip)] + k: Kind, +} + +fn main() { + let opts = Opts::from_args(); + println!("{:?}", opts); +} diff --git a/tests/ui/skip_without_default.stderr b/tests/ui/skip_without_default.stderr new file mode 100644 index 00000000..62d976ef --- /dev/null +++ b/tests/ui/skip_without_default.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `Kind: std::default::Default` is not satisfied + --> $DIR/skip_without_default.rs:17:10 + | +17 | #[derive(StructOpt, Debug)] + | ^^^^^^^^^ the trait `std::default::Default` is not implemented for `Kind` + | + = note: required by `std::default::Default::default` + +For more information about this error, try `rustc --explain E0277`. From 0e0e7968f5d64a2a4c2084f9c6c8ce92b8a79dc3 Mon Sep 17 00:00:00 2001 From: Ivan Veselov Date: Wed, 3 Jul 2019 20:41:30 +0100 Subject: [PATCH 2/2] Add test for flatten/subcommand/skip combinations. --- structopt-derive/src/attrs.rs | 2 +- tests/ui/skip_flatten.rs | 42 ++++++++++++++++++++++++++ tests/ui/skip_flatten.stderr | 7 +++++ tests/ui/skip_subcommand.rs | 42 ++++++++++++++++++++++++++ tests/ui/skip_subcommand.stderr | 7 +++++ tests/ui/subcommand_and_flatten.stderr | 2 +- 6 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 tests/ui/skip_flatten.rs create mode 100644 tests/ui/skip_flatten.stderr create mode 100644 tests/ui/skip_subcommand.rs create mode 100644 tests/ui/skip_subcommand.stderr diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index 92f95738..7f2337d7 100755 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -466,7 +466,7 @@ impl Attrs { if let Kind::Arg(_) = self.kind { self.kind = kind; } else { - panic!("subcommands cannot be flattened"); + panic!("subcommand, flatten and skip cannot be used together"); } } pub fn has_method(&self, method: &str) -> bool { diff --git a/tests/ui/skip_flatten.rs b/tests/ui/skip_flatten.rs new file mode 100644 index 00000000..5c14e5f9 --- /dev/null +++ b/tests/ui/skip_flatten.rs @@ -0,0 +1,42 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(skip, flatten)] + cmd: Command, +} + +#[derive(StructOpt)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +impl Default for Command { + fn default() -> Self { + Pound { acorns: 0 } + } +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/tests/ui/skip_flatten.stderr b/tests/ui/skip_flatten.stderr new file mode 100644 index 00000000..d09320b6 --- /dev/null +++ b/tests/ui/skip_flatten.stderr @@ -0,0 +1,7 @@ +error: proc-macro derive panicked + --> $DIR/skip_flatten.rs:11:10 + | +11 | #[derive(StructOpt)] + | ^^^^^^^^^ + | + = help: message: subcommand, flatten and skip cannot be used together diff --git a/tests/ui/skip_subcommand.rs b/tests/ui/skip_subcommand.rs new file mode 100644 index 00000000..e512290b --- /dev/null +++ b/tests/ui/skip_subcommand.rs @@ -0,0 +1,42 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, skip)] + cmd: Command, +} + +#[derive(StructOpt)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +impl Default for Command { + fn default() -> Self { + Pound { acorns: 0 } + } +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/tests/ui/skip_subcommand.stderr b/tests/ui/skip_subcommand.stderr new file mode 100644 index 00000000..9156b11f --- /dev/null +++ b/tests/ui/skip_subcommand.stderr @@ -0,0 +1,7 @@ +error: proc-macro derive panicked + --> $DIR/skip_subcommand.rs:11:10 + | +11 | #[derive(StructOpt)] + | ^^^^^^^^^ + | + = help: message: subcommand, flatten and skip cannot be used together diff --git a/tests/ui/subcommand_and_flatten.stderr b/tests/ui/subcommand_and_flatten.stderr index ce49d7ce..f2f835a6 100644 --- a/tests/ui/subcommand_and_flatten.stderr +++ b/tests/ui/subcommand_and_flatten.stderr @@ -4,4 +4,4 @@ error: proc-macro derive panicked 11 | #[derive(StructOpt)] | ^^^^^^^^^ | - = help: message: subcommands cannot be flattened + = help: message: subcommand, flatten and skip cannot be used together