update syn quote and proc_macro2 + fmt

This commit is contained in:
Guillaume Pinot 2018-05-21 16:54:22 +02:00
parent 87daa0bc8e
commit 01d34fb8d2
21 changed files with 769 additions and 383 deletions

View file

@ -8,6 +8,9 @@ matrix:
include:
- rust: nightly
env: FEATURES="--features nightly"
- rust: stable
env: RUN=FMT
before_script: rustup component add rustfmt-preview
script: cargo fmt --all -- --write-mode diff
script:
- cargo test $FEATURES

View file

@ -1,4 +1,5 @@
#[macro_use] extern crate structopt;
#[macro_use]
extern crate structopt;
use structopt::StructOpt;
@ -10,7 +11,7 @@ pub struct Foo {
#[derive(Debug, StructOpt)]
pub enum Command {
#[structopt(name = "foo")]
Foo(Foo)
Foo(Foo),
}
#[derive(Debug, StructOpt)]

View file

@ -3,7 +3,8 @@
//! Documentation can be added either through doc comments or the
//! `about` attribute.
#[macro_use] extern crate structopt;
#[macro_use]
extern crate structopt;
use structopt::StructOpt;
@ -19,7 +20,7 @@ enum Opt {
#[structopt(long = "all")]
all: bool,
#[structopt(default_value = "origin")]
repository: String
repository: String,
},
#[structopt(name = "add")]
/// add files to the staging area
@ -28,8 +29,8 @@ enum Opt {
interactive: bool,
#[structopt(short = "a")]
all: bool,
files: Vec<String>
}
files: Vec<String>,
},
}
fn main() {

View file

@ -14,8 +14,7 @@ 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)
ArgGroup::with_name("vers").required(true)
}
#[derive(StructOpt, Debug)]

View file

@ -1,8 +1,8 @@
#[macro_use]
extern crate structopt;
use structopt::StructOpt;
use std::error::Error;
use structopt::StructOpt;
fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<Error>>
where
@ -11,7 +11,8 @@ where
U: std::str::FromStr,
U::Err: Error + 'static,
{
let pos = s.find('=').ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
let pos = s.find('=')
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}

View file

@ -7,8 +7,7 @@ use structopt::clap::AppSettings;
#[derive(StructOpt, Debug)]
#[structopt(name = "no_version", about = "", version = "", author = "",
raw(global_settings = "&[AppSettings::DisableVersion]"))]
struct Opt {
}
struct Opt {}
fn main() {
let opt = Opt::from_args();

View file

@ -411,7 +411,10 @@ pub trait StructOpt {
/// Gets the struct from the command line arguments. Print the
/// error message and quit the program in case of failure.
fn from_args() -> Self where Self: Sized {
fn from_args() -> Self
where
Self: Sized,
{
Self::from_clap(&Self::clap().get_matches())
}
@ -421,7 +424,7 @@ pub trait StructOpt {
where
Self: Sized,
I: IntoIterator,
I::Item: Into<OsString> + Clone
I::Item: Into<OsString> + Clone,
{
Self::from_clap(&Self::clap().get_matches_from(iter))
}
@ -435,7 +438,7 @@ pub trait StructOpt {
where
Self: Sized,
I: IntoIterator,
I::Item: Into<OsString> + Clone
I::Item: Into<OsString> + Clone,
{
Ok(Self::from_clap(&Self::clap().get_matches_from_safe(iter)?))
}

View file

@ -13,9 +13,9 @@ license = "Apache-2.0/MIT"
travis-ci = { repository = "TeXitoi/structopt" }
[dependencies]
syn = "0.13"
quote = "0.5"
proc-macro2 = "0.3"
syn = "0.14"
quote = "0.6"
proc-macro2 = "0.4"
[features]
nightly = ["proc-macro2/nightly"]

View file

@ -6,10 +6,10 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use proc_macro2::{Span, TokenStream};
use std::{env, mem};
use quote::Tokens;
use syn::{self, Attribute, MetaNameValue, MetaList, LitStr, TypePath};
use syn::Type::Path;
use syn::{self, Attribute, Ident, LitStr, MetaList, MetaNameValue, TypePath};
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Kind {
@ -28,14 +28,14 @@ pub enum Ty {
pub struct Attrs {
name: String,
methods: Vec<Method>,
parser: (Parser, Tokens),
parser: (Parser, TokenStream),
has_custom_parser: bool,
kind: Kind,
}
#[derive(Debug)]
struct Method {
name: String,
args: Tokens,
args: TokenStream,
}
#[derive(Debug, PartialEq)]
pub enum Parser {
@ -54,7 +54,7 @@ impl ::std::str::FromStr for Parser {
"from_os_str" => Ok(Parser::FromOsStr),
"try_from_os_str" => Ok(Parser::TryFromOsStr),
"from_occurrences" => Ok(Parser::FromOccurrences),
_ => Err(format!("unsupported parser {}", s))
_ => Err(format!("unsupported parser {}", s)),
}
}
}
@ -73,10 +73,7 @@ impl Attrs {
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();
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 {
@ -86,22 +83,23 @@ impl Attrs {
}
}
fn push_attrs(&mut self, attrs: &[Attribute]) {
use Lit::*;
use Meta::*;
use NestedMeta::*;
use Lit::*;
let iter = attrs.iter()
let iter = attrs
.iter()
.filter_map(|attr| {
let path = &attr.path;
match quote!(#path) == quote!(structopt) {
match quote!(#path).to_string() == "structopt" {
true => Some(
attr.interpret_meta()
.expect(&format!("invalid structopt syntax: {}", quote!(attr)))
.expect(&format!("invalid structopt syntax: {}", quote!(attr))),
),
false => None,
}
}).
flat_map(|m| match m {
})
.flat_map(|m| match m {
List(l) => l.nested,
tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
})
@ -110,46 +108,55 @@ impl Attrs {
ref tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
});
for attr in iter {
match attr {
NameValue(MetaNameValue { ident, lit: Str(ref value), .. }) =>
self.push_str_method(ident.as_ref(), &value.value()),
NameValue(MetaNameValue { ident, lit, .. }) => {
self.methods.push(Method {
name: ident.to_string(),
args: quote!(#lit),
})
}
List(MetaList { ident, ref nested, .. }) if ident == "parse" => {
match &attr {
NameValue(MetaNameValue {
ident,
lit: Str(value),
..
}) => self.push_str_method(&ident.to_string(), &value.value()),
NameValue(MetaNameValue { ident, lit, .. }) => self.methods.push(Method {
name: ident.to_string(),
args: quote!(#lit),
}),
List(MetaList { ident, nested, .. }) if ident == "parse" => {
if nested.len() != 1 {
panic!("parse must have exactly one argument");
}
self.has_custom_parser = true;
self.parser = match nested[0] {
Meta(NameValue(MetaNameValue { ident, lit: Str(ref v), .. })) => {
self.parser = match &nested[0] {
Meta(NameValue(MetaNameValue {
ident, lit: Str(v), ..
})) => {
let function: syn::Path = v.parse().expect("parser function path");
let parser = ident.as_ref().parse().unwrap();
let parser = ident.to_string().parse().unwrap();
(parser, quote!(#function))
}
Meta(Word(ref i)) => {
use Parser::*;
let parser = i.as_ref().parse().unwrap();
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 _}),
TryFromOsStr => panic!(
"cannot omit parser function name with `try_from_os_str`"
),
FromOccurrences => quote!({ |v| v as _ }),
};
(parser, function)
}
ref l @ _ => panic!("unknown value parser specification: {}", quote!(#l)),
};
}
List(MetaList { ident, ref nested, .. }) if ident == "raw" => {
List(MetaList {
ident, ref nested, ..
}) if ident == "raw" =>
{
for method in nested {
match *method {
Meta(NameValue(MetaNameValue { ident, lit: Str(ref v), .. })) =>
self.push_raw_method(ident.as_ref(), v),
match method {
Meta(NameValue(MetaNameValue {
ident, lit: Str(v), ..
})) => self.push_raw_method(&ident.to_string(), v),
ref mi @ _ => panic!("unsupported raw entry: {}", quote!(#mi)),
}
}
@ -160,33 +167,41 @@ impl Attrs {
Word(ref w) if w == "flatten" => {
self.set_kind(Kind::FlattenStruct);
}
ref i @ List(..) | ref i @ Word(..) =>
panic!("unsupported option: {}", quote!(#i)),
ref i @ List(..) | ref i @ Word(..) => panic!("unsupported option: {}", quote!(#i)),
}
}
}
fn push_raw_method(&mut self, name: &str, args: &LitStr) {
let ts: ::proc_macro2::TokenStream = args.value().parse()
.expect(&format!("bad parameter {} = {}: the parameter must be valid rust code", name, quote!(#args)));
let ts: TokenStream = args.value().parse().expect(&format!(
"bad parameter {} = {}: the parameter must be valid rust code",
name,
quote!(#args)
));
self.methods.push(Method {
name: name.to_string(),
args: quote!(#(#ts)*),
})
}
fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
let doc_comments: Vec<_> = attrs.iter()
let doc_comments: Vec<_> = attrs
.iter()
.filter_map(|attr| {
let path = &attr.path;
match quote!(#path) == quote!(doc) {
match quote!(#path).to_string() == "doc" {
true => attr.interpret_meta(),
false => None,
}
})
.filter_map(|attr| {
use Meta::*;
use Lit::*;
if let NameValue(MetaNameValue { ident, lit: Str(s), .. }) = attr {
if ident != "doc" { return None; }
use Meta::*;
if let NameValue(MetaNameValue {
ident, lit: Str(s), ..
}) = attr
{
if ident != "doc" {
return None;
}
let value = s.value();
let text = value
.trim_left_matches("//!")
@ -197,7 +212,7 @@ impl Attrs {
.trim();
if text.is_empty() {
Some("\n\n".to_string())
} else{
} else {
Some(text.to_string())
}
} else {
@ -205,7 +220,9 @@ impl Attrs {
}
})
.collect();
if doc_comments.is_empty() { return; }
if doc_comments.is_empty() {
return;
}
let arg = doc_comments
.join(" ")
.split('\n')
@ -224,7 +241,8 @@ impl Attrs {
("about", "CARGO_PKG_DESCRIPTION"),
("author", "CARGO_PKG_AUTHORS"),
];
attrs_with_env.iter()
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)| {
@ -247,8 +265,12 @@ impl Attrs {
}
}
fn ty_from_field(ty: &syn::Type) -> Ty {
if let Path(TypePath { path: syn::Path { ref segments, .. }, .. }) = *ty {
match segments.iter().last().unwrap().ident.as_ref() {
if let Path(TypePath {
path: syn::Path { ref segments, .. },
..
}) = *ty
{
match segments.iter().last().unwrap().ident.to_string().as_str() {
"bool" => Ty::Bool,
"Option" => Ty::Option,
"Vec" => Ty::Vec,
@ -259,7 +281,7 @@ impl Attrs {
}
}
pub fn from_field(field: &syn::Field) -> Attrs {
let name = field.ident.as_ref().unwrap().as_ref().to_string();
let name = field.ident.as_ref().unwrap().to_string();
let mut res = Self::new(name);
res.push_doc_comment(&field.attrs, "help");
res.push_attrs(&field.attrs);
@ -298,7 +320,7 @@ impl Attrs {
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")
@ -306,7 +328,7 @@ impl Attrs {
if res.has_method("required") {
panic!("required is meaningless for Option")
}
},
}
_ => (),
}
res.kind = Kind::Arg(ty);
@ -325,9 +347,9 @@ impl Attrs {
pub fn has_method(&self, method: &str) -> bool {
self.methods.iter().find(|m| m.name == method).is_some()
}
pub fn methods(&self) -> Tokens {
pub fn methods(&self) -> TokenStream {
let methods = self.methods.iter().map(|&Method { ref name, ref args }| {
let name: ::syn::Ident = name.as_str().into();
let name = Ident::new(&name, Span::call_site());
quote!( .#name(#args) )
});
quote!( #(#methods)* )
@ -335,7 +357,7 @@ impl Attrs {
pub fn name(&self) -> &str {
&self.name
}
pub fn parser(&self) -> &(Parser, Tokens) {
pub fn parser(&self) -> &(Parser, TokenStream) {
&self.parser
}
pub fn kind(&self) -> Kind {

View file

@ -18,15 +18,15 @@ extern crate proc_macro2;
mod attrs;
use proc_macro::TokenStream;
use syn::*;
use attrs::{Attrs, Kind, Parser, Ty};
use proc_macro2::{Span, TokenStream};
use syn::punctuated::Punctuated;
use syn::token::{Comma};
use attrs::{Attrs, Parser, Kind, Ty};
use syn::token::Comma;
use syn::*;
/// Generates the `StructOpt` impl.
#[proc_macro_derive(StructOpt, attributes(structopt))]
pub fn structopt(input: TokenStream) -> TokenStream {
pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = syn::parse(input).unwrap();
let gen = impl_structopt(&input);
gen.into()
@ -34,16 +34,19 @@ pub fn structopt(input: TokenStream) -> TokenStream {
fn sub_type(t: &syn::Type) -> Option<&syn::Type> {
let segs = match *t {
syn::Type::Path(TypePath { path: syn::Path { ref segments, .. }, .. }) => segments,
syn::Type::Path(TypePath {
path: syn::Path { ref segments, .. },
..
}) => segments,
_ => return None,
};
match *segs.iter().last().unwrap() {
PathSegment {
arguments: PathArguments::AngleBracketed(
AngleBracketedGenericArguments { ref args, .. }
),
arguments:
PathArguments::AngleBracketed(AngleBracketedGenericArguments { ref args, .. }),
..
} if args.len() == 1 => {
} if args.len() == 1 =>
{
if let GenericArgument::Type(ref ty) = args[0] {
Some(ty)
} else {
@ -56,19 +59,24 @@ fn sub_type(t: &syn::Type) -> Option<&syn::Type> {
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an app.
fn gen_augmentation(fields: &Punctuated<Field, Comma>, app_var: &Ident) -> quote::Tokens {
let subcmds: Vec<quote::Tokens> = fields.iter()
fn gen_augmentation(fields: &Punctuated<Field, Comma>, app_var: &Ident) -> TokenStream {
let subcmds: Vec<_> = fields
.iter()
.filter_map(|field| {
let attrs = Attrs::from_field(&field);
if let Kind::Subcommand(ty) = attrs.kind() {
let subcmd_type = match (ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty
_ => &field.ty,
};
let required = if ty == Ty::Option {
quote!()
} else {
quote!( let #app_var = #app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp); )
quote! {
let #app_var = #app_var.setting(
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
);
}
};
Some(quote!{
@ -81,64 +89,70 @@ fn gen_augmentation(fields: &Punctuated<Field, Comma>, app_var: &Ident) -> quote
})
.collect();
assert!(subcmds.len() <= 1, "cannot have more than one nested subcommand");
assert!(
subcmds.len() <= 1,
"cannot have more than one nested subcommand"
);
let args = fields.iter()
.filter_map(|field| {
let attrs = Attrs::from_field(field);
match attrs.kind() {
Kind::Subcommand(_) => None,
Kind::FlattenStruct => {
let ty = &field.ty;
Some(quote! {
let #app_var = <#ty>::augment_clap(#app_var);
let #app_var = if <#ty>::is_subcommand() {
#app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp)
} else {
#app_var
};
})
}
Kind::Arg(ty) => {
let convert_type = match ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
_ => &field.ty,
let args = fields.iter().filter_map(|field| {
let attrs = Attrs::from_field(field);
match attrs.kind() {
Kind::Subcommand(_) => None,
Kind::FlattenStruct => {
let ty = &field.ty;
Some(quote! {
let #app_var = <#ty>::augment_clap(#app_var);
let #app_var = if <#ty>::is_subcommand() {
#app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp)
} else {
#app_var
};
let occurences = attrs.parser().0 == Parser::FromOccurrences;
let validator = match *attrs.parser() {
(Parser::TryFromStr, ref f) => quote! {
.validator(|s| {
#f(&s)
.map(|_: #convert_type| ())
.map_err(|e| e.to_string())
})
},
(Parser::TryFromOsStr, ref f) => quote! {
.validator_os(|s| #f(&s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
let modifier = match ty {
Ty::Bool => quote!( .takes_value(false).multiple(false) ),
Ty::Option => quote!( .takes_value(true).multiple(false) #validator ),
Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ),
Ty::Other if occurences => quote!( .takes_value(false).multiple(true) ),
Ty::Other => {
let required = !attrs.has_method("default_value");
quote!( .takes_value(true).multiple(false).required(#required) #validator )
},
};
let methods = attrs.methods();
let name = attrs.name();
Some(quote!{
let #app_var = #app_var.arg(::structopt::clap::Arg::with_name(#name)#modifier#methods);
})
}
})
}
});
Kind::Arg(ty) => {
let convert_type = match ty {
Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
_ => &field.ty,
};
let occurences = attrs.parser().0 == Parser::FromOccurrences;
let validator = match *attrs.parser() {
(Parser::TryFromStr, ref f) => quote! {
.validator(|s| {
#f(&s)
.map(|_: #convert_type| ())
.map_err(|e| e.to_string())
})
},
(Parser::TryFromOsStr, ref f) => quote! {
.validator_os(|s| #f(&s).map(|_: #convert_type| ()))
},
_ => quote!(),
};
let modifier = match ty {
Ty::Bool => quote!( .takes_value(false).multiple(false) ),
Ty::Option => quote!( .takes_value(true).multiple(false) #validator ),
Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ),
Ty::Other if occurences => quote!( .takes_value(false).multiple(true) ),
Ty::Other => {
let required = !attrs.has_method("default_value");
quote!( .takes_value(true).multiple(false).required(#required) #validator )
}
};
let methods = attrs.methods();
let name = attrs.name();
Some(quote!{
let #app_var = #app_var.arg(
::structopt::clap::Arg::with_name(#name)
#modifier
#methods
);
})
}
}
});
quote! {{
#( #args )*
@ -147,7 +161,7 @@ fn gen_augmentation(fields: &Punctuated<Field, Comma>, app_var: &Ident) -> quote
}}
}
fn gen_constructor(fields: &Punctuated<Field, Comma>) -> quote::Tokens {
fn gen_constructor(fields: &Punctuated<Field, Comma>) -> TokenStream {
let fields = fields.iter().map(|field| {
let attrs = Attrs::from_field(field);
let field_name = field.ident.as_ref().unwrap();
@ -155,27 +169,30 @@ fn gen_constructor(fields: &Punctuated<Field, Comma>) -> quote::Tokens {
Kind::Subcommand(ty) => {
let subcmd_type = match (ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty
_ => &field.ty,
};
let unwrapper = match ty {
Ty::Option => quote!(),
_ => quote!( .unwrap() )
_ => quote!( .unwrap() ),
};
quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper)
}
Kind::FlattenStruct => {
quote!(#field_name: StructOpt::from_clap(matches))
}
Kind::FlattenStruct => quote!(#field_name: StructOpt::from_clap(matches)),
Kind::Arg(ty) => {
use Parser::*;
let (value_of, values_of, parse) = match *attrs.parser() {
(FromStr, ref f) => (quote!(value_of), quote!(values_of), f.clone()),
(TryFromStr, ref f) =>
(quote!(value_of), quote!(values_of), quote!(|s| #f(s).unwrap())),
(FromOsStr, ref f) =>
(quote!(value_of_os), quote!(values_of_os), f.clone()),
(TryFromOsStr, ref f) =>
(quote!(value_of_os), quote!(values_of_os), quote!(|s| #f(s).unwrap())),
(TryFromStr, ref f) => (
quote!(value_of),
quote!(values_of),
quote!(|s| #f(s).unwrap()),
),
(FromOsStr, ref f) => (quote!(value_of_os), quote!(values_of_os), f.clone()),
(TryFromOsStr, ref f) => (
quote!(value_of_os),
quote!(values_of_os),
quote!(|s| #f(s).unwrap()),
),
(FromOccurrences, ref f) => (quote!(occurrences_of), quote!(), f.clone()),
};
@ -213,7 +230,7 @@ fn gen_constructor(fields: &Punctuated<Field, Comma>) -> quote::Tokens {
}}
}
fn gen_from_clap(struct_name: &Ident, fields: &Punctuated<Field, Comma>) -> quote::Tokens {
fn gen_from_clap(struct_name: &Ident, fields: &Punctuated<Field, Comma>) -> TokenStream {
let field_block = gen_constructor(fields);
quote! {
@ -223,15 +240,17 @@ fn gen_from_clap(struct_name: &Ident, fields: &Punctuated<Field, Comma>) -> quot
}
}
fn gen_clap(attrs: &[Attribute]) -> quote::Tokens {
let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_else(String::default);
fn gen_clap(attrs: &[Attribute]) -> TokenStream {
let name = std::env::var("CARGO_PKG_NAME")
.ok()
.unwrap_or_else(String::default);
let attrs = Attrs::from_struct(attrs, name);
let name = attrs.name();
let methods = attrs.methods();
quote!(::structopt::clap::App::new(#name)#methods)
}
fn gen_clap_struct(struct_attrs: &[Attribute]) -> quote::Tokens {
fn gen_clap_struct(struct_attrs: &[Attribute]) -> TokenStream {
let gen = gen_clap(struct_attrs);
quote! {
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
@ -241,17 +260,19 @@ fn gen_clap_struct(struct_attrs: &[Attribute]) -> quote::Tokens {
}
}
fn gen_augment_clap(fields: &Punctuated<Field, Comma>) -> quote::Tokens {
let app_var: Ident = "app".into();
fn gen_augment_clap(fields: &Punctuated<Field, Comma>) -> TokenStream {
let app_var = Ident::new("app", Span::call_site());
let augmentation = gen_augmentation(fields, &app_var);
quote! {
pub fn augment_clap<'a, 'b>(#app_var: ::structopt::clap::App<'a, 'b>) -> ::structopt::clap::App<'a, 'b> {
pub fn augment_clap<'a, 'b>(
#app_var: ::structopt::clap::App<'a, 'b>
) -> ::structopt::clap::App<'a, 'b> {
#augmentation
}
}
}
fn gen_clap_enum(enum_attrs: &[Attribute]) -> quote::Tokens {
fn gen_clap_enum(enum_attrs: &[Attribute]) -> TokenStream {
let gen = gen_clap(enum_attrs);
quote! {
fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
@ -262,13 +283,13 @@ fn gen_clap_enum(enum_attrs: &[Attribute]) -> quote::Tokens {
}
}
fn gen_augment_clap_enum(variants: &Punctuated<Variant, Comma>) -> quote::Tokens {
fn gen_augment_clap_enum(variants: &Punctuated<Variant, Comma>) -> TokenStream {
use syn::Fields::*;
let subcommands = variants.iter().map(|variant| {
let name = variant.ident.as_ref().to_string();
let name = variant.ident.to_string();
let attrs = Attrs::from_struct(&variant.attrs, name);
let app_var: Ident = "subcommand".into();
let app_var = Ident::new("subcommand", Span::call_site());
let arg_block = match variant.fields {
Named(ref fields) => gen_augmentation(&fields.named, &app_var),
Unit => quote!( #app_var ),
@ -278,7 +299,9 @@ fn gen_augment_clap_enum(variants: &Punctuated<Variant, Comma>) -> quote::Tokens
{
let #app_var = <#ty>::augment_clap(#app_var);
if <#ty>::is_subcommand() {
#app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp)
#app_var.setting(
::structopt::clap::AppSettings::SubcommandRequiredElseHelp
)
} else {
#app_var
}
@ -300,13 +323,15 @@ fn gen_augment_clap_enum(variants: &Punctuated<Variant, Comma>) -> quote::Tokens
});
quote! {
pub fn augment_clap<'a, 'b>(app: ::structopt::clap::App<'a, 'b>) -> ::structopt::clap::App<'a, 'b> {
pub fn augment_clap<'a, 'b>(
app: ::structopt::clap::App<'a, 'b>
) -> ::structopt::clap::App<'a, 'b> {
app #( #subcommands )*
}
}
}
fn gen_from_clap_enum(name: &Ident) -> quote::Tokens {
fn gen_from_clap_enum(name: &Ident) -> TokenStream {
quote! {
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
<#name>::from_subcommand(matches.subcommand())
@ -315,11 +340,11 @@ fn gen_from_clap_enum(name: &Ident) -> quote::Tokens {
}
}
fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> quote::Tokens {
fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> TokenStream {
use syn::Fields::*;
let match_arms = variants.iter().map(|variant| {
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.as_ref().to_string());
let attrs = Attrs::from_struct(&variant.attrs, variant.ident.to_string());
let sub_name = attrs.name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
@ -329,8 +354,7 @@ fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> q
let ty = &fields.unnamed[0];
quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) )
}
Unnamed(..) =>
panic!("{}: tuple enum are not supported", variant.ident),
Unnamed(..) => panic!("{}: tuple enum are not supported", variant.ident),
};
quote! {
@ -340,7 +364,9 @@ fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> q
});
quote! {
pub fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>)) -> Option<Self> {
pub fn from_subcommand<'a, 'b>(
sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>)
) -> Option<Self> {
match sub {
#( #match_arms ),*,
_ => None
@ -352,8 +378,8 @@ fn gen_from_subcommand(name: &Ident, variants: &Punctuated<Variant, Comma>) -> q
fn impl_structopt_for_struct(
name: &Ident,
fields: &Punctuated<Field, Comma>,
attrs: &[Attribute]
) -> quote::Tokens {
attrs: &[Attribute],
) -> TokenStream {
let clap = gen_clap_struct(attrs);
let augment_clap = gen_augment_clap(fields);
let from_clap = gen_from_clap(name, fields);
@ -377,8 +403,8 @@ fn impl_structopt_for_struct(
fn impl_structopt_for_enum(
name: &Ident,
variants: &Punctuated<Variant, Comma>,
attrs: &[Attribute]
) -> quote::Tokens {
attrs: &[Attribute],
) -> TokenStream {
let clap = gen_clap_enum(attrs);
let augment_clap = gen_augment_clap_enum(variants);
let from_clap = gen_from_clap_enum(name);
@ -400,16 +426,17 @@ fn impl_structopt_for_enum(
}
}
fn impl_structopt(input: &DeriveInput) -> quote::Tokens {
fn impl_structopt(input: &DeriveInput) -> TokenStream {
use syn::Data::*;
let struct_name = &input.ident;
let inner_impl = match input.data {
Struct(DataStruct { fields: syn::Fields::Named(ref fields), .. }) =>
impl_structopt_for_struct(struct_name, &fields.named, &input.attrs),
Enum(ref e) =>
impl_structopt_for_enum(struct_name, &e.variants, &input.attrs),
_ => panic!("structopt only supports non-tuple structs and enums")
Struct(DataStruct {
fields: syn::Fields::Named(ref fields),
..
}) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs),
Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs),
_ => panic!("structopt only supports non-tuple structs and enums"),
};
quote!(#inner_impl)

View file

@ -20,7 +20,11 @@ fn required_argument() {
}
assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test", "42"]));
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "42", "24"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "42", "24"])
.is_err()
);
}
#[test]
@ -31,7 +35,11 @@ fn optional_argument() {
}
assert_eq!(Opt { arg: Some(42) }, Opt::from_iter(&["test", "42"]));
assert_eq!(Opt { arg: None }, Opt::from_iter(&["test"]));
assert!(Opt::clap().get_matches_from_safe(&["test", "42", "24"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "42", "24"])
.is_err()
);
}
#[test]
@ -43,7 +51,11 @@ fn argument_with_default() {
}
assert_eq!(Opt { arg: 24 }, Opt::from_iter(&["test", "24"]));
assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test"]));
assert!(Opt::clap().get_matches_from_safe(&["test", "42", "24"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "42", "24"])
.is_err()
);
}
#[test]
@ -55,7 +67,11 @@ fn argument_with_raw_default() {
}
assert_eq!(Opt { arg: 24 }, Opt::from_iter(&["test", "24"]));
assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test"]));
assert!(Opt::clap().get_matches_from_safe(&["test", "42", "24"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "42", "24"])
.is_err()
);
}
#[test]
@ -66,19 +82,27 @@ fn arguments() {
}
assert_eq!(Opt { arg: vec![24] }, Opt::from_iter(&["test", "24"]));
assert_eq!(Opt { arg: vec![] }, Opt::from_iter(&["test"]));
assert_eq!(Opt { arg: vec![24, 42] }, Opt::from_iter(&["test", "24", "42"]));
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::from_iter(&["test", "24", "42"])
);
}
#[test]
fn arguments_safe() {
#[derive(StructOpt, PartialEq, Debug)]
struct Opt {
arg: Vec<i32>,
}
assert_eq!(Opt { arg: vec![24] }, Opt::from_iter_safe(&["test", "24"]).unwrap());
assert_eq!(
Opt { arg: vec![24] },
Opt::from_iter_safe(&["test", "24"]).unwrap()
);
assert_eq!(Opt { arg: vec![] }, Opt::from_iter_safe(&["test"]).unwrap());
assert_eq!(Opt { arg: vec![24, 42] }, Opt::from_iter_safe(&["test", "24", "42"]).unwrap());
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::from_iter_safe(&["test", "24", "42"]).unwrap()
);
assert_eq!(
clap::ErrorKind::ValueValidation,

View file

@ -6,7 +6,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[macro_use] extern crate structopt;
#[macro_use]
extern crate structopt;
use structopt::StructOpt;

View file

@ -11,9 +11,9 @@ extern crate structopt;
use structopt::StructOpt;
use std::path::PathBuf;
use std::num::ParseIntError;
use std::ffi::{OsStr, OsString};
use std::num::ParseIntError;
use std::path::PathBuf;
#[derive(StructOpt, PartialEq, Debug)]
struct PathOpt {
@ -48,19 +48,11 @@ fn test_path_opt_simple() {
option_path_2: Some(PathBuf::from("j.zip")),
},
PathOpt::from_clap(&PathOpt::clap().get_matches_from(&[
"test",
"-p", "/usr/bin",
"-v", "/a/b/c",
"-v", "/d/e/f",
"-v", "/g/h/i",
"-q", "j.zip",
"test", "-p", "/usr/bin", "-v", "/a/b/c", "-v", "/d/e/f", "-v", "/g/h/i", "-q", "j.zip"
]))
);
}
fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(input, 16)
}
@ -82,13 +74,12 @@ fn test_parse_hex() {
HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"]))
);
let err = HexOpt::clap().get_matches_from_safe(&["test", "-n", "gg"]).unwrap_err();
let err = HexOpt::clap()
.get_matches_from_safe(&["test", "-n", "gg"])
.unwrap_err();
assert!(err.message.contains("invalid digit found in string"), err);
}
fn custom_parser_1(_: &str) -> &'static str {
"A"
}
@ -117,14 +108,16 @@ struct NoOpOpt {
#[test]
fn test_every_custom_parser() {
assert_eq!(
NoOpOpt { a: "A", b: "B", c: "C", d: "D" },
NoOpOpt::from_clap(&NoOpOpt::clap().get_matches_from(&[
"test", "-a=?", "-b=?", "-c=?", "-d=?",
]))
NoOpOpt {
a: "A",
b: "B",
c: "C",
d: "D"
},
NoOpOpt::from_clap(&NoOpOpt::clap().get_matches_from(&["test", "-a=?", "-b=?", "-c=?", "-d=?",]))
);
}
// Note: can't use `Vec<u8>` directly, as structopt would instead look for
// conversion function from `&str` to `u8`.
type Bytes = Vec<u8>;
@ -151,14 +144,16 @@ fn test_parser_with_default_value() {
},
DefaultedOpt::from_clap(&DefaultedOpt::clap().get_matches_from(&[
"test",
"-b", "E²=p²c²+m²c⁴",
"-i", "9000",
"-p", "src/lib.rs",
"-b",
"E²=p²c²+m²c⁴",
"-i",
"9000",
"-p",
"src/lib.rs",
]))
);
}
#[derive(PartialEq, Debug)]
struct Foo(u8);
@ -223,33 +218,72 @@ fn test_custom_bool() {
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "-d"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "-dfoo"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-dfoo"])
.is_err()
);
assert_eq!(
Opt { debug: false, verbose: false, tribool: None, bitset: vec![] },
Opt {
debug: false,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::from_iter(&["test", "-dfalse"]),
);
assert_eq!(
Opt { debug: true, verbose: false, tribool: None, bitset: vec![] },
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::from_iter(&["test", "-dtrue"]),
);
assert_eq!(
Opt { debug: true, verbose: false, tribool: None, bitset: vec![] },
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![],
},
Opt::from_iter(&["test", "-dtrue", "-vfalse"]),
);
assert_eq!(
Opt { debug: true, verbose: true, tribool: None, bitset: vec![] },
Opt {
debug: true,
verbose: true,
tribool: None,
bitset: vec![],
},
Opt::from_iter(&["test", "-dtrue", "-vtrue"]),
);
assert_eq!(
Opt { debug: true, verbose: false, tribool: Some(false), bitset: vec![] },
Opt {
debug: true,
verbose: false,
tribool: Some(false),
bitset: vec![],
},
Opt::from_iter(&["test", "-dtrue", "-tfalse"]),
);
assert_eq!(
Opt { debug: true, verbose: false, tribool: Some(true), bitset: vec![] },
Opt {
debug: true,
verbose: false,
tribool: Some(true),
bitset: vec![],
},
Opt::from_iter(&["test", "-dtrue", "-ttrue"]),
);
assert_eq!(
Opt { debug: true, verbose: false, tribool: None, bitset: vec![false, true, false, false] },
Opt::from_iter(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse","-bfalse"]),
Opt {
debug: true,
verbose: false,
tribool: None,
bitset: vec![false, true, false, false],
},
Opt::from_iter(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"]),
);
}

View file

@ -7,7 +7,7 @@
// except according to those terms.
#![deny(warnings)]
#![cfg(feature = "nightly")]// TODO: remove that when never is stable
#![cfg(feature = "nightly")] // TODO: remove that when never is stable
#![feature(never_type)]
#[macro_use]
@ -26,9 +26,12 @@ fn warning_never_struct() {
#[structopt(parse(try_from_str = "try_str"))]
s: String,
}
assert_eq!(Opt { s: "foo".to_string() },
Opt::from_iter(&["test", "foo"]));
assert_eq!(
Opt {
s: "foo".to_string()
},
Opt::from_iter(&["test", "foo"])
);
}
#[test]
@ -38,10 +41,12 @@ fn warning_never_enum() {
Foo {
#[structopt(parse(try_from_str = "try_str"))]
s: String,
}
},
}
assert_eq!(Opt::Foo { s: "foo".to_string() },
Opt::from_iter(&["test", "Foo", "foo"]));
assert_eq!(
Opt::Foo {
s: "foo".to_string()
},
Opt::from_iter(&["test", "Foo", "foo"])
);
}

View file

@ -19,16 +19,34 @@ fn unique_flag() {
alice: bool,
}
assert_eq!(Opt { alice: false },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"])));
assert_eq!(Opt { alice: true },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])));
assert_eq!(Opt { alice: true },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice"])));
assert_eq!(
Opt { alice: false },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
);
assert_eq!(
Opt { alice: true },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
);
assert_eq!(
Opt { alice: true },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice"]))
);
assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "foo"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "-a"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "--alice"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a", "foo"])
.is_err()
);
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a", "-a"])
.is_err()
);
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a", "--alice"])
.is_err()
);
}
#[test]
@ -41,18 +59,32 @@ fn multiple_flag() {
bob: u8,
}
assert_eq!(Opt { alice: 0, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"])));
assert_eq!(Opt { alice: 1, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])));
assert_eq!(Opt { alice: 2, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-a"])));
assert_eq!(Opt { alice: 2, bob: 2 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--alice", "-bb"])));
assert_eq!(Opt { alice: 3, bob: 1 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-aaa", "--bob"])));
assert_eq!(
Opt { alice: 0, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
);
assert_eq!(
Opt { alice: 1, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
);
assert_eq!(
Opt { alice: 2, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-a"]))
);
assert_eq!(
Opt { alice: 2, bob: 2 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--alice", "-bb"]))
);
assert_eq!(
Opt { alice: 3, bob: 1 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-aaa", "--bob"]))
);
assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "foo"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a", "foo"])
.is_err()
);
}
#[test]
@ -65,16 +97,46 @@ fn combined_flags() {
bob: u64,
}
assert_eq!(Opt { alice: false, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"])));
assert_eq!(Opt { alice: true, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])));
assert_eq!(Opt { alice: true, bob: 0 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])));
assert_eq!(Opt { alice: false, bob: 1 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"])));
assert_eq!(Opt { alice: true, bob: 1 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice", "--bob"])));
assert_eq!(Opt { alice: true, bob: 4 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-bb", "-a", "-bb"])));
assert_eq!(
Opt {
alice: false,
bob: 0
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
);
assert_eq!(
Opt {
alice: true,
bob: 0
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
);
assert_eq!(
Opt {
alice: true,
bob: 0
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"]))
);
assert_eq!(
Opt {
alice: false,
bob: 1
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"]))
);
assert_eq!(
Opt {
alice: true,
bob: 1
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice", "--bob"]))
);
assert_eq!(
Opt {
alice: true,
bob: 4
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-bb", "-a", "-bb"]))
);
}

View file

@ -23,9 +23,18 @@ fn flatten() {
#[structopt(flatten)]
common: Common,
}
assert_eq!(Opt { common: Common { arg: 42 } }, Opt::from_iter(&["test", "42"]));
assert_eq!(
Opt {
common: Common { arg: 42 }
},
Opt::from_iter(&["test", "42"])
);
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "42", "24"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "42", "24"])
.is_err()
);
}
#[test]
@ -76,8 +85,18 @@ fn flatten_in_subcommand() {
Add(Add),
}
assert_eq!(Opt::Fetch { all: false, common: Common { arg: 42 } },
Opt::from_iter(&["test", "fetch", "42"]));
assert_eq!(Opt::Add(Add { interactive: true, common: Common { arg: 43 } }),
Opt::from_iter(&["test", "add", "-i", "43"]));
assert_eq!(
Opt::Fetch {
all: false,
common: Common { arg: 42 }
},
Opt::from_iter(&["test", "fetch", "42"])
);
assert_eq!(
Opt::Add(Add {
interactive: true,
common: Common { arg: 43 }
}),
Opt::from_iter(&["test", "add", "-i", "43"])
);
}

View file

@ -6,7 +6,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[macro_use] extern crate structopt;
#[macro_use]
extern crate structopt;
use structopt::StructOpt;
@ -17,7 +18,7 @@ struct Opt {
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
verbose: u64,
#[structopt(subcommand)]
cmd: Sub
cmd: Sub,
}
#[derive(StructOpt, PartialEq, Debug)]
@ -25,7 +26,7 @@ enum Sub {
#[structopt(name = "fetch")]
Fetch {},
#[structopt(name = "add")]
Add {}
Add {},
}
#[derive(StructOpt, PartialEq, Debug)]
@ -35,7 +36,7 @@ struct Opt2 {
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
verbose: u64,
#[structopt(subcommand)]
cmd: Option<Sub>
cmd: Option<Sub>,
}
#[test]
@ -43,24 +44,54 @@ fn test_no_cmd() {
let result = Opt::clap().get_matches_from_safe(&["test"]);
assert!(result.is_err());
assert_eq!(Opt2 { force: false, verbose: 0, cmd: None },
Opt2::from_clap(&Opt2::clap().get_matches_from(&["test"])));
assert_eq!(
Opt2 {
force: false,
verbose: 0,
cmd: None
},
Opt2::from_clap(&Opt2::clap().get_matches_from(&["test"]))
);
}
#[test]
fn test_fetch() {
assert_eq!(Opt { force: false, verbose: 3, cmd: Sub::Fetch {} },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vvv", "fetch"])));
assert_eq!(Opt { force: true, verbose: 0, cmd: Sub::Fetch {} },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--force", "fetch"])));
assert_eq!(
Opt {
force: false,
verbose: 3,
cmd: Sub::Fetch {}
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vvv", "fetch"]))
);
assert_eq!(
Opt {
force: true,
verbose: 0,
cmd: Sub::Fetch {}
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--force", "fetch"]))
);
}
#[test]
fn test_add() {
assert_eq!(Opt { force: false, verbose: 0, cmd: Sub::Add {} },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"])));
assert_eq!(Opt { force: false, verbose: 2, cmd: Sub::Add {} },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vv", "add"])));
assert_eq!(
Opt {
force: false,
verbose: 0,
cmd: Sub::Add {}
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"]))
);
assert_eq!(
Opt {
force: false,
verbose: 2,
cmd: Sub::Add {}
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vv", "add"]))
);
}
#[test]
@ -80,7 +111,7 @@ struct Opt3 {
#[structopt(short = "a", long = "all")]
all: bool,
#[structopt(subcommand)]
cmd: Sub2
cmd: Sub2,
}
#[derive(StructOpt, PartialEq, Debug)]
@ -89,11 +120,10 @@ enum Sub2 {
Foo {
file: String,
#[structopt(subcommand)]
cmd: Sub3
cmd: Sub3,
},
#[structopt(name = "bar")]
Bar {
}
Bar {},
}
#[derive(StructOpt, PartialEq, Debug)]
@ -101,7 +131,7 @@ enum Sub3 {
#[structopt(name = "baz")]
Baz {},
#[structopt(name = "quux")]
Quux {}
Quux {},
}
#[test]
@ -109,7 +139,10 @@ fn test_subsubcommand() {
assert_eq!(
Opt3 {
all: true,
cmd: Sub2::Foo { file: "lib.rs".to_string(), cmd: Sub3::Quux {} }
cmd: Sub2::Foo {
file: "lib.rs".to_string(),
cmd: Sub3::Quux {}
}
},
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "--all", "foo", "lib.rs", "quux"]))
);
@ -117,43 +150,57 @@ fn test_subsubcommand() {
#[derive(StructOpt, PartialEq, Debug)]
enum SubSubCmdWithOption {
#[structopt(name = "remote")]
#[structopt(name = "remote")]
Remote {
#[structopt(subcommand)]
cmd: Option<Remote>
cmd: Option<Remote>,
},
#[structopt(name = "stash")]
Stash {
#[structopt(subcommand)]
cmd: Stash
cmd: Stash,
},
}
#[derive(StructOpt, PartialEq, Debug)]
enum Remote {
#[structopt(name = "add")]
#[structopt(name = "add")]
Add { name: String, url: String },
#[structopt(name = "remove")]
#[structopt(name = "remove")]
Remove { name: String },
}
#[derive(StructOpt, PartialEq, Debug)]
enum Stash {
#[structopt(name = "save")]
#[structopt(name = "save")]
Save,
#[structopt(name = "pop")]
#[structopt(name = "pop")]
Pop,
}
#[test]
fn sub_sub_cmd_with_option() {
fn make(args: &[&str]) -> Option<SubSubCmdWithOption> {
SubSubCmdWithOption::clap().get_matches_from_safe(args).ok().map(|m| SubSubCmdWithOption::from_clap(&m))
SubSubCmdWithOption::clap()
.get_matches_from_safe(args)
.ok()
.map(|m| SubSubCmdWithOption::from_clap(&m))
}
assert_eq!(Some(SubSubCmdWithOption::Remote { cmd: None }), make(&["", "remote"]));
assert_eq!(
Some(SubSubCmdWithOption::Remote { cmd: Some(Remote::Add { name: "origin".into(), url: "http".into() }) }),
Some(SubSubCmdWithOption::Remote { cmd: None }),
make(&["", "remote"])
);
assert_eq!(
Some(SubSubCmdWithOption::Remote {
cmd: Some(Remote::Add {
name: "origin".into(),
url: "http".into()
})
}),
make(&["", "remote", "add", "origin", "http"])
);
assert_eq!(Some(SubSubCmdWithOption::Stash { cmd: Stash::Save }), make(&["", "stash", "save"]));
assert_eq!(
Some(SubSubCmdWithOption::Stash { cmd: Stash::Save }),
make(&["", "stash", "save"])
);
assert_eq!(None, make(&["", "stash"]));
}

View file

@ -18,14 +18,24 @@ fn required_option() {
#[structopt(short = "a", long = "arg")]
arg: i32,
}
assert_eq!(Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])));
assert_eq!(Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "42"])));
assert_eq!(Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--arg", "42"])));
assert_eq!(
Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"]))
);
assert_eq!(
Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "42"]))
);
assert_eq!(
Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--arg", "42"]))
);
assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
assert!(Opt::clap().get_matches_from_safe(&["test", "-a42", "-a24"]).is_err());
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a42", "-a24"])
.is_err()
);
}
#[test]
@ -35,11 +45,19 @@ fn optional_option() {
#[structopt(short = "a")]
arg: Option<i32>,
}
assert_eq!(Opt { arg: Some(42) },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])));
assert_eq!(Opt { arg: None },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"])));
assert!(Opt::clap().get_matches_from_safe(&["test", "-a42", "-a24"]).is_err());
assert_eq!(
Opt { arg: Some(42) },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"]))
);
assert_eq!(
Opt { arg: None },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
);
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a42", "-a24"])
.is_err()
);
}
#[test]
@ -49,11 +67,19 @@ fn option_with_default() {
#[structopt(short = "a", default_value = "42")]
arg: i32,
}
assert_eq!(Opt { arg: 24 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])));
assert_eq!(Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"])));
assert!(Opt::clap().get_matches_from_safe(&["test", "-a42", "-a24"]).is_err());
assert_eq!(
Opt { arg: 24 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"]))
);
assert_eq!(
Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
);
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a42", "-a24"])
.is_err()
);
}
#[test]
@ -63,11 +89,19 @@ fn option_with_raw_default() {
#[structopt(short = "a", raw(default_value = r#""42""#))]
arg: i32,
}
assert_eq!(Opt { arg: 24 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])));
assert_eq!(Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"])));
assert!(Opt::clap().get_matches_from_safe(&["test", "-a42", "-a24"]).is_err());
assert_eq!(
Opt { arg: 24 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"]))
);
assert_eq!(
Opt { arg: 42 },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
);
assert!(
Opt::clap()
.get_matches_from_safe(&["test", "-a42", "-a24"])
.is_err()
);
}
#[test]
@ -77,12 +111,18 @@ fn options() {
#[structopt(short = "a", long = "arg")]
arg: Vec<i32>,
}
assert_eq!(Opt { arg: vec![24] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])));
assert_eq!(Opt { arg: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"])));
assert_eq!(Opt { arg: vec![24, 42] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24", "--arg", "42"])));
assert_eq!(
Opt { arg: vec![24] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"]))
);
assert_eq!(
Opt { arg: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test"]))
);
assert_eq!(
Opt { arg: vec![24, 42] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24", "--arg", "42"]))
);
}
#[test]
@ -93,5 +133,8 @@ fn empy_default_value() {
arg: String,
}
assert_eq!(Opt { arg: "".into() }, Opt::from_iter(&["test"]));
assert_eq!(Opt { arg: "foo".into() }, Opt::from_iter(&["test", "-afoo"]));
assert_eq!(
Opt { arg: "foo".into() },
Opt::from_iter(&["test", "-afoo"])
);
}

View file

@ -12,18 +12,18 @@ extern crate structopt;
mod options {
#[derive(Debug, StructOpt)]
pub struct Options {
#[structopt(subcommand)]
pub subcommand: ::subcommands::SubCommand,
#[structopt(subcommand)]
pub subcommand: ::subcommands::SubCommand,
}
}
mod subcommands {
#[derive(Debug, StructOpt)]
pub enum SubCommand {
#[structopt(name = "foo", about = "foo")]
Foo {
#[structopt(help = "foo")]
bars: Vec<String>,
},
#[structopt(name = "foo", about = "foo")]
Foo {
#[structopt(help = "foo")]
bars: Vec<String>,
},
}
}

View file

@ -17,8 +17,8 @@ use structopt::clap::AppSettings;
#[structopt(raw(global_settings = "&[AppSettings::ColoredHelp]"))]
struct Opt {
#[structopt(long = "x",
raw(display_order = "2", next_line_help = "true",
default_value = r#""0""#, require_equals = "true"))]
raw(display_order = "2", next_line_help = "true", default_value = r#""0""#,
require_equals = "true"))]
x: i32,
#[structopt(short = "l", long = "level", raw(aliases = r#"&["set-level", "lvl"]"#))]
@ -33,22 +33,64 @@ struct Opt {
#[test]
fn test_raw_slice() {
assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1"])));
assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--level", "1"])));
assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--set-level", "1"])));
assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--lvl", "1"])));
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1"]))
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--level", "1"]))
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--set-level", "1"]))
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: Vec::new(),
values: vec![],
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--lvl", "1"]))
);
}
#[test]
fn test_raw_multi_args() {
assert_eq!(Opt { x: 0, level: "1".to_string(), files: vec!["file".to_string()], values: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "file"])));
assert_eq!(Opt { x: 0, level: "1".to_string(), files: vec!["FILE".to_string()], values: vec![1] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--values", "1", "--", "FILE"])));
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: vec!["file".to_string()],
values: vec![],
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "file"]))
);
assert_eq!(
Opt {
x: 0,
level: "1".to_string(),
files: vec!["FILE".to_string()],
values: vec![1],
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--values", "1", "--", "FILE"]))
);
}
#[test]
@ -59,8 +101,15 @@ fn test_raw_multi_args_fail() {
#[test]
fn test_raw_bool() {
assert_eq!(Opt { x: 1, level: "1".to_string(), files: vec![], values: vec![] },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--x=1"])));
assert_eq!(
Opt {
x: 1,
level: "1".to_string(),
files: vec![],
values: vec![],
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--x=1"]))
);
let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--x", "1"]);
assert!(result.is_err());
}

View file

@ -6,7 +6,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[macro_use] extern crate structopt;
#[macro_use]
extern crate structopt;
use structopt::StructOpt;
@ -19,32 +20,54 @@ enum Opt {
#[structopt(short = "f", long = "force")]
/// Overwrite local branches.
force: bool,
repo: String
repo: String,
},
#[structopt(name = "add")]
Add {
#[structopt(short = "i", long = "interactive")]
interactive: bool,
#[structopt(short = "v", long = "verbose")]
verbose: bool
}
verbose: bool,
},
}
#[test]
fn test_fetch() {
assert_eq!(Opt::Fetch { all: true, force: false, repo: "origin".to_string() },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "--all", "origin"])));
assert_eq!(Opt::Fetch { all: false, force: true, repo: "origin".to_string() },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "-f", "origin"])));
assert_eq!(
Opt::Fetch {
all: true,
force: false,
repo: "origin".to_string()
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "--all", "origin"]))
);
assert_eq!(
Opt::Fetch {
all: false,
force: true,
repo: "origin".to_string()
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "-f", "origin"]))
);
}
#[test]
fn test_add() {
assert_eq!(Opt::Add { interactive: false, verbose: false },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"])));
assert_eq!(Opt::Add { interactive: true, verbose: true },
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add", "-i", "-v"])));
assert_eq!(
Opt::Add {
interactive: false,
verbose: false
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"]))
);
assert_eq!(
Opt::Add {
interactive: true,
verbose: true
},
Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add", "-i", "-v"]))
);
}
#[test]
@ -62,17 +85,19 @@ fn test_no_parse() {
#[derive(StructOpt, PartialEq, Debug)]
enum Opt2 {
#[structopt(name = "do-something")]
DoSomething {
arg: String
}
DoSomething { arg: String },
}
#[test]
/// This test is specifically to make sure that hyphenated subcommands get
/// processed correctly.
fn test_hyphenated_subcommands() {
assert_eq!(Opt2::DoSomething { arg: "blah".to_string() },
Opt2::from_clap(&Opt2::clap().get_matches_from(&["test", "do-something", "blah"])));
assert_eq!(
Opt2::DoSomething {
arg: "blah".to_string()
},
Opt2::from_clap(&Opt2::clap().get_matches_from(&["test", "do-something", "blah"]))
);
}
#[derive(StructOpt, PartialEq, Debug)]
@ -82,14 +107,23 @@ enum Opt3 {
#[structopt(name = "init")]
Init,
#[structopt(name = "fetch")]
Fetch
Fetch,
}
#[test]
fn test_null_commands() {
assert_eq!(Opt3::Add, Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "add"])));
assert_eq!(Opt3::Init, Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "init"])));
assert_eq!(Opt3::Fetch, Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "fetch"])));
assert_eq!(
Opt3::Add,
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "add"]))
);
assert_eq!(
Opt3::Init,
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "init"]))
);
assert_eq!(
Opt3::Fetch,
Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "fetch"]))
);
}
#[derive(StructOpt, PartialEq, Debug)]
@ -117,12 +151,19 @@ enum Opt4 {
#[test]
fn test_tuple_commands() {
assert_eq!(
Opt4::Add(Add { file: "f".to_string() }),
Opt4::Add(Add {
file: "f".to_string()
}),
Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "add", "f"]))
);
assert_eq!(Opt4::Init, Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "init"])));
assert_eq!(
Opt4::Fetch(Fetch { remote: "origin".to_string() }),
Opt4::Init,
Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "init"]))
);
assert_eq!(
Opt4::Fetch(Fetch {
remote: "origin".to_string()
}),
Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "fetch", "origin"]))
);
@ -140,7 +181,7 @@ fn enum_in_enum_subsubcommand() {
#[derive(StructOpt, Debug, PartialEq)]
pub enum Opt {
#[structopt(name = "daemon")]
Daemon(DaemonCommand)
Daemon(DaemonCommand),
}
#[derive(StructOpt, Debug, PartialEq)]
@ -175,5 +216,10 @@ fn flatten_enum() {
}
assert!(Opt::from_iter_safe(&["test"]).is_err());
assert_eq!(Opt::from_iter(&["test", "Foo"]), Opt { sub_cmd: SubCmd::Foo });
assert_eq!(
Opt::from_iter(&["test", "Foo"]),
Opt {
sub_cmd: SubCmd::Foo
}
);
}