Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,36 @@ extern crate structopt_derive;

use structopt::StructOpt;

/// A basic example
#[derive(StructOpt, Debug)]
#[structopt(name = "basic", about = "A basic example")]
#[structopt(name = "basic")]
struct Opt {
#[structopt(short = "d", long = "debug", help = "Activate debug mode")]
/// Activate debug mode
#[structopt(short = "d", long = "debug")]
debug: bool,
#[structopt(short = "v", long = "verbose", help = "Verbose mode")]

/// Verbose mode
#[structopt(short = "v", long = "verbose")]
verbose: u64,
#[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")]

/// Set speed
#[structopt(short = "s", long = "speed", default_value = "42")]
speed: f64,
#[structopt(short = "o", long = "output", help = "Output file")]

/// Output file
#[structopt(short = "o", long = "output")]
output: String,
#[structopt(short = "c", long = "car", help = "Number of car")]

/// Number of car
#[structopt(short = "c", long = "car")]
car: Option<i32>,

/// admin_level to consider
#[structopt(short = "l", long = "level")]
#[structopt(help = "admin_level to consider")]
level: Vec<String>,
#[structopt(name = "FILE", help = "Files to process")]

/// Files to process
#[structopt(name = "FILE")]
files: Vec<String>,
}

Expand Down
65 changes: 49 additions & 16 deletions structopt-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,31 +129,64 @@ fn sub_type(t: &syn::Ty) -> Option<&syn::Ty> {
}
}

fn extract_attrs<'a>(attrs: &'a [Attribute]) -> Box<Iterator<Item = (&'a Ident, &'a Lit)> + 'a> {
let iter = attrs.iter()
#[derive(Debug, Clone, Copy)]
enum AttrSource { Struct, Field, }

fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box<Iterator<Item = (Ident, Lit)> + 'a> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was pretty close to making this a Box<Iterator<Item = (Cow<'a, Ident>, Cow<'a, Lit>)> but luckily I noticed early enough that this was derive macro whose performance will probably never matter 😅

let settings_attrs = attrs.iter()
.filter_map(|attr| match attr.value {
MetaItem::List(ref i, ref v) if i.as_ref() == "structopt" => Some(v),
_ => None,
}).flat_map(|v| v.iter().filter_map(|mi| match *mi {
NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, ref l)) => Some((i, l)),
NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, ref l)) =>
Some((i.clone(), l.clone())),
_ => None,
}));
Box::new(iter)

let doc_comments = attrs.iter()
.filter_map(move |attr| {
if let Attribute {
value: MetaItem::NameValue(ref name, Lit::Str(ref value, StrStyle::Cooked)),
is_sugared_doc: true,
..
} = *attr {
if name != "doc" { return None; }
let text = value.trim_left_matches("//!")
.trim_left_matches("///")
.trim_left_matches("/*!")
.trim_left_matches("/**")
.trim();

// Clap's `App` has an `about` method to set a description,
// it's `Field`s have a `help` method instead.
if let AttrSource::Struct = attr_source {
Some(("about".into(), text.into()))
} else {
Some(("help".into(), text.into()))
}
} else {
None
}
});

Box::new(doc_comments.chain(settings_attrs))
}

fn from_attr_or_env(attrs: &[(&Ident, &Lit)], key: &str, env: &str) -> Lit {
fn from_attr_or_env(attrs: &[(Ident, Lit)], key: &str, env: &str) -> Lit {
let default = std::env::var(env).unwrap_or("".into());
attrs.iter()
.find(|&&(i, _)| i.as_ref() == key)
.map(|&(_, l)| l.clone())
.filter(|&&(ref i, _)| i.as_ref() == key)
.last()
.map(|&(_, ref l)| l.clone())
.unwrap_or_else(|| Lit::Str(default, StrStyle::Cooked))
}

fn gen_name(field: &Field) -> Ident {
extract_attrs(&field.attrs)
.find(|&(i, _)| i.as_ref() == "name")
.and_then(|(_, l)| match *l {
Lit::Str(ref s, _) => Some(Ident::new(s.clone())),
extract_attrs(&field.attrs, AttrSource::Field)
.filter(|&(ref i, _)| i.as_ref() == "name")
.last()
.and_then(|(_, ref l)| match l {
&Lit::Str(ref s, _) => Some(Ident::new(s.clone())),
_ => None,
})
.unwrap_or(field.ident.as_ref().unwrap().clone())
Expand Down Expand Up @@ -196,7 +229,7 @@ fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens {
}

fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens {
let struct_attrs: Vec<_> = extract_attrs(&ast.attrs).collect();
let struct_attrs: Vec<_> = extract_attrs(&ast.attrs, AttrSource::Struct).collect();
let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME");
let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION");
let author = from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS");
Expand All @@ -220,14 +253,14 @@ fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens {
Ty::Option => quote!( .takes_value(true).multiple(false).#validator ),
Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ),
Ty::Other => {
let required = extract_attrs(&field.attrs)
.find(|&(i, _)| i.as_ref() == "default_value")
let required = extract_attrs(&field.attrs, AttrSource::Field)
.find(|&(ref i, _)| i.as_ref() == "default_value")
.is_none();
quote!( .takes_value(true).multiple(false).required(#required).#validator )
},
};
let from_attr = extract_attrs(&field.attrs)
.filter(|&(i, _)| i.as_ref() != "name")
let from_attr = extract_attrs(&field.attrs, AttrSource::Field)
.filter(|&(ref i, _)| i.as_ref() != "name")
.map(|(i, l)| quote!(.#i(#l)));
quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) )
});
Expand Down
51 changes: 51 additions & 0 deletions tests/doc-comments-help.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2017 structopt Developers
//
// This work is free. You can redistribute it and/or modify it under
// the terms of the Do What The Fuck You Want To Public License,
// Version 2, as published by Sam Hocevar. See the COPYING file for
// more details.

extern crate structopt;
#[macro_use]
extern crate structopt_derive;

use structopt::StructOpt;

#[test]
fn commets_intead_of_actual_help() {
/// Lorem ipsum
#[derive(StructOpt, PartialEq, Debug)]
struct LoremIpsum {
/// Fooify a bar
#[structopt(short = "f", long = "foo")]
foo: bool,
}

let mut output = Vec::new();
LoremIpsum::clap().write_long_help(&mut output).unwrap();
let output = String::from_utf8(output).unwrap();

assert!(output.contains("Lorem ipsum"));
assert!(output.contains("Fooify a bar"));
}

#[test]
fn help_is_better_than_comments() {
/// Lorem ipsum
#[derive(StructOpt, PartialEq, Debug)]
#[structopt(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum {
/// Fooify a bar
#[structopt(short = "f", long = "foo", help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")]
foo: bool,
}

let mut output = Vec::new();
LoremIpsum::clap().write_long_help(&mut output).unwrap();
let output = String::from_utf8(output).unwrap();

println!("{}", output);
assert!(output.contains("Dolor sit amet"));
assert!(!output.contains("Lorem ipsum"));
assert!(output.contains("DO NOT PASS A BAR"));
}