Skip to content

Commit

Permalink
Merge pull request #334 from TeXitoi/flatten-for-enums
Browse files Browse the repository at this point in the history
  • Loading branch information
CreepySkeleton authored Jan 18, 2020
2 parents dc66640 + 9a696ce commit c9f9087
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 94 deletions.
33 changes: 32 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
//! - [Subcommands](#subcommands)
//! - [Optional subcommands](#optional-subcommands)
//! - [External subcommands](#external-subcommands)
//! - [Flattening subcommands](#flattening-subcommands)
//! - [Flattening](#flattening)
//! - [Custom string parsers](#custom-string-parsers)
//!
Expand Down Expand Up @@ -280,7 +281,7 @@
//!
//! - [`flatten`](#flattening): `flatten`
//!
//! Usable only on field-level.
//! Usable on field-level or single-typed tuple variants.
//!
//! - [`subcommand`](#subcommands): `subcommand`
//!
Expand Down Expand Up @@ -926,6 +927,36 @@
//!
//! [`AppSettings::AllowExternalSubcommands`]: https://docs.rs/clap/2.32.0/clap/enum.AppSettings.html#variant.AllowExternalSubcommands
//!
//! ### Flattening subcommands
//!
//! It is also possible to combine multiple enums of subcommands into one.
//! All the subcommands will be on the same level.
//!
//! ```
//! # use structopt::StructOpt;
//! # fn main() {}
//! #[derive(StructOpt)]
//! enum BaseCli {
//! Ghost10 {
//! arg1: i32,
//! }
//! }
//!
//! #[derive(StructOpt)]
//! enum Opt {
//! #[structopt(flatten)]
//! BaseCli(BaseCli),
//! Dex {
//! arg2: i32,
//! }
//! }
//! ```
//!
//! ```shell
//! cli ghost10 42
//! cli dex 42
//! ```
//!
//! ## Flattening
//!
//! It can sometimes be useful to group related arguments in a substruct,
Expand Down
9 changes: 4 additions & 5 deletions structopt-derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub enum Kind {
Arg(Sp<Ty>),
Subcommand(Sp<Ty>),
ExternalSubcommand,
FlattenStruct,
Flatten,
Skip(Option<Expr>),
}

Expand Down Expand Up @@ -282,7 +282,7 @@ impl Attrs {
}

Flatten(ident) => {
let kind = Sp::new(Kind::FlattenStruct, ident.span());
let kind = Sp::new(Kind::Flatten, ident.span());
self.set_kind(kind);
}

Expand Down Expand Up @@ -406,9 +406,8 @@ impl Attrs {
}
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(_) | Kind::ExternalSubcommand => res,
Kind::Arg(_) | Kind::ExternalSubcommand | Kind::Flatten => res,
}
}

Expand All @@ -431,7 +430,7 @@ impl Attrs {
res.push_attrs(&field.attrs);

match &*res.kind {
Kind::FlattenStruct => {
Kind::Flatten => {
if res.has_custom_parser {
abort!(
res.parser.span(),
Expand Down
212 changes: 135 additions & 77 deletions structopt-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ fn gen_augmentation(
"`external_subcommand` is only allowed on enum variants"
),
Kind::Subcommand(_) | Kind::Skip(_) => None,
Kind::FlattenStruct => {
Kind::Flatten => {
let ty = &field.ty;
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(#app_var);
Expand Down Expand Up @@ -270,7 +270,7 @@ fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs)
}
}

Kind::FlattenStruct => quote_spanned! { kind.span()=>
Kind::Flatten => quote_spanned! { kind.span()=>
#field_name: ::structopt::StructOpt::from_clap(matches)
},

Expand Down Expand Up @@ -468,45 +468,67 @@ fn gen_augment_clap_enum(
parent_attribute.env_casing(),
);

if let Kind::ExternalSubcommand = *attrs.kind() {
return quote_spanned! { attrs.kind().span()=>
.setting(::structopt::clap::AppSettings::AllowExternalSubcommands)
};
}
let kind = attrs.kind();
match &*kind {
Kind::ExternalSubcommand => {
quote_spanned! { attrs.kind().span()=>
let app = app.setting(
::structopt::clap::AppSettings::AllowExternalSubcommands
);
}
},

let app_var = Ident::new("subcommand", Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs),
Unit => quote!( #app_var ),
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote_spanned! { ty.span()=>
{
let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(
#app_var
);
if <#ty as ::structopt::StructOptInternal>::is_subcommand() {
#app_var.setting(
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
)
} else {
#app_var
Kind::Flatten => {
match variant.fields {
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote! {
let app = <#ty as ::structopt::StructOptInternal>::augment_clap(app);
}
}
},
_ => abort!(
variant.span(),
"`flatten` is usable only with single-typed tuple variants"
),
}
}
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
},

let name = attrs.cased_name();
let from_attrs = attrs.top_level_methods();
let version = attrs.version();
quote! {
.subcommand({
let #app_var = ::structopt::clap::SubCommand::with_name(#name);
let #app_var = #arg_block;
#app_var#from_attrs#version
})
_ => {
let app_var = Ident::new("subcommand", Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs),
Unit => quote!( #app_var ),
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
quote_spanned! { ty.span()=>
{
let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(
#app_var
);
if <#ty as ::structopt::StructOptInternal>::is_subcommand() {
#app_var.setting(
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
)
} else {
#app_var
}
}
}
}
Unnamed(..) => abort!(variant.span(), "non single-typed tuple enums are not supported"),
};

let name = attrs.cased_name();
let from_attrs = attrs.top_level_methods();
let version = attrs.version();
quote! {
let app = app.subcommand({
let #app_var = ::structopt::clap::SubCommand::with_name(#name);
let #app_var = #arg_block;
#app_var#from_attrs#version
});
}
},
}
});

Expand All @@ -516,7 +538,9 @@ fn gen_augment_clap_enum(
fn augment_clap<'a, 'b>(
app: ::structopt::clap::App<'a, 'b>
) -> ::structopt::clap::App<'a, 'b> {
app #app_methods #( #subcommands )* #version
let app = app #app_methods;
#( #subcommands )*;
app #version
}
}
}
Expand All @@ -539,7 +563,7 @@ fn gen_from_subcommand(

let mut ext_subcmd = None;

let match_arms: Vec<_> = variants
let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
.iter()
.filter_map(|variant| {
let attrs = Attrs::from_struct(
Expand All @@ -551,7 +575,6 @@ fn gen_from_subcommand(
parent_attribute.env_casing(),
);

let sub_name = attrs.cased_name();
let variant_name = &variant.ident;

if let Kind::ExternalSubcommand = *attrs.kind() {
Expand Down Expand Up @@ -601,59 +624,94 @@ fn gen_from_subcommand(
ext_subcmd = Some((span, variant_name, str_ty, values_of));
None
} else {
let constructor_block = match variant.fields {
Named(ref fields) => gen_constructor(&fields.named, &attrs),
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) )
}
Unnamed(..) => {
abort_call_site!("{}: tuple enums are not supported", variant.ident)
}
};

Some(quote! {
(#sub_name, Some(matches)) =>
Some(#name :: #variant_name #constructor_block)
})
Some((variant, attrs))
}
})
.collect();
.partition(|(_, attrs)| match &*attrs.kind() {
Kind::Flatten => true,
_ => false,
});

let wildcard = match ext_subcmd {
let external = match ext_subcmd {
Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=>
("", ::std::option::Option::None) => None,

(external, Some(matches)) => {
::std::option::Option::Some(#name::#var_name(
::std::iter::once(#str_ty::from(external))
.chain(
matches.#values_of("").unwrap().map(#str_ty::from)
)
.collect::<::std::vec::Vec<_>>()
))
}
match other {
("", ::std::option::Option::None) => None,

(external, Some(matches)) => {
::std::option::Option::Some(#name::#var_name(
::std::iter::once(#str_ty::from(external))
.chain(
matches.#values_of("").unwrap().map(#str_ty::from)
)
.collect::<::std::vec::Vec<_>>()
))
}

(external, None) => {
::std::option::Option::Some(#name::#var_name({
let mut v = ::std::vec::Vec::with_capacity(1);
v.push(#str_ty::from(external));
v
}))
(external, None) => {
::std::option::Option::Some(#name::#var_name({
::std::iter::once(#str_ty::from(external))
.collect::<::std::vec::Vec<_>>()
}))
}
}
},

None => quote!(_ => None),
None => quote!(None),
};

let match_arms = variants.iter().map(|(variant, attrs)| {
let sub_name = attrs.cased_name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
Named(ref fields) => gen_constructor(&fields.named, &attrs),
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) )
}
Unnamed(..) => abort!(
variant.ident.span(),
"non single-typed tuple enums are not supported"
),
};

quote! {
(#sub_name, Some(matches)) => {
Some(#name :: #variant_name #constructor_block)
}
}
});

let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| {
let variant_name = &variant.ident;
match variant.fields {
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
quote! {
if let Some(res) =
<#ty as ::structopt::StructOptInternal>::from_subcommand(other)
{
return Some(#name :: #variant_name (res));
}
}
}
_ => abort!(
variant.span(),
"`flatten` is usable only with single-typed tuple variants"
),
}
});

quote! {
fn from_subcommand<'a, 'b>(
sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>)
) -> Option<Self> {
match sub {
#( #match_arms, )*
#wildcard
other => {
#( #child_subcommands )else*;
#external
}
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions structopt-derive/src/spanned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,18 @@ impl<'a> From<Sp<&'a str>> for Sp<String> {
}
}

impl<U, T: PartialEq<U>> PartialEq<U> for Sp<T> {
fn eq(&self, other: &U) -> bool {
impl<T: PartialEq> PartialEq<T> for Sp<T> {
fn eq(&self, other: &T) -> bool {
self.val == *other
}
}

impl<T: PartialEq> PartialEq for Sp<T> {
fn eq(&self, other: &Sp<T>) -> bool {
self.val == **other
}
}

impl<T: AsRef<str>> AsRef<str> for Sp<T> {
fn as_ref(&self) -> &str {
self.val.as_ref()
Expand Down
Loading

0 comments on commit c9f9087

Please sign in to comment.