Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Auto-detect FromStr, TryFrom<&OsStr>, etc., in clap_derive #2298

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ clap = "3.0.0-beta.2"

The first example shows the simplest way to use `clap`, by defining a struct. If you're familiar with the `structopt` crate you're in luck, it's the same! (In fact it's the exact same code running under the covers!)

Clap introduces the additional convenience of auto-detecting whether a type implements `FromStr`, `From<&OsStr>`, `TryFrom<&OsStr>` and other standard parsing traits, so in many cases it's no longer necessary to use attributes such as `parse(try_from_str)`, `parse(from_os_str)`, `parse(try_from_os_str)`, and similar.

```rust,no_run
// (Full example with detailed comments in examples/01d_quick_example.rs)
//
Expand Down
1 change: 1 addition & 0 deletions clap_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ proc-macro-error = "1"
[dev-dependencies]
clap = { path = "../" }
clap_generate = { path = "../clap_generate" }
os_str_bytes = { version = "2.4" }
trybuild = "1.0"
rustversion = "1"
version-sync = "0.8"
Expand Down
4 changes: 2 additions & 2 deletions clap_derive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct Opt {
speed: f64,

/// Output file
#[clap(short, long, parse(from_os_str), value_hint = ValueHint::FilePath)]
#[clap(short, long, value_hint = ValueHint::FilePath)]
output: PathBuf,

// the long option will be translated by default to kebab case,
Expand All @@ -56,7 +56,7 @@ struct Opt {
level: Vec<String>,

/// Files to process
#[clap(name = "FILE", parse(from_os_str), value_hint = ValueHint::AnyPath)]
#[clap(name = "FILE", value_hint = ValueHint::AnyPath)]
files: Vec<PathBuf>,
}

Expand Down
4 changes: 2 additions & 2 deletions clap_derive/examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct Opt {
speed: f64,

/// Output file
#[clap(short, long, parse(from_os_str), value_hint = ValueHint::FilePath)]
#[clap(short, long, value_hint = ValueHint::FilePath)]
output: PathBuf,

// the long option will be translated by default to kebab case,
Expand All @@ -38,7 +38,7 @@ struct Opt {
level: Vec<String>,

/// Files to process
#[clap(name = "FILE", parse(from_os_str), value_hint = ValueHint::AnyPath)]
#[clap(name = "FILE", value_hint = ValueHint::AnyPath)]
files: Vec<PathBuf>,
}

Expand Down
2 changes: 1 addition & 1 deletion clap_derive/examples/value_hints_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct Opt {
dir: Option<PathBuf>,
#[clap(short, long, value_hint = ValueHint::ExecutablePath)]
exe: Option<PathBuf>,
#[clap(long, parse(from_os_str), value_hint = ValueHint::CommandName)]
#[clap(long, value_hint = ValueHint::CommandName)]
cmd_name: Option<OsString>,
#[clap(short, long, value_hint = ValueHint::CommandString)]
cmd: Option<String>,
Expand Down
61 changes: 28 additions & 33 deletions clap_derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake;
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum Kind {
Arg(Sp<Ty>),
Arg(Sp<Ty>, TokenStream),
Subcommand(Sp<Ty>),
Flatten,
Skip(Option<Expr>),
Expand All @@ -53,11 +53,12 @@ pub struct Method {
#[derive(Clone)]
pub struct Parser {
pub kind: Sp<ParserKind>,
pub func: TokenStream,
pub parse_func: Option<Expr>,
}

#[derive(Debug, PartialEq, Clone)]
pub enum ParserKind {
Auto,
FromStr,
TryFromStr,
FromOsStr,
Expand Down Expand Up @@ -155,15 +156,16 @@ impl ToTokens for Method {

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)
let kind = Sp::new(ParserKind::Auto, span);
let parse_func = None;
Sp::new(Parser { kind, parse_func }, span)
}

fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
use self::ParserKind::*;

let kind = match &*spec.kind.to_string() {
"auto" => Auto,
"from_str" => FromStr,
"try_from_str" => TryFromStr,
"from_os_str" => FromOsStr,
Expand All @@ -173,28 +175,11 @@ impl Parser {
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 {
Expr::Path(_) => quote!(#func),
_ => abort!(func, "`parse` argument must be a function path"),
},
};

let kind = Sp::new(kind, spec.kind.span());
let parser = Parser { kind, func };
let parser = Parser {
kind,
parse_func: spec.parse_func,
};
Sp::new(parser, parse_ident.span())
}
}
Expand Down Expand Up @@ -283,7 +268,10 @@ impl Attrs {
verbatim_doc_comment: None,
is_enum: false,
has_custom_parser: false,
kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
kind: Sp::new(
Kind::Arg(Sp::new(Ty::Other, default_span), TokenStream::new()),
default_span,
),
}
}

Expand Down Expand Up @@ -453,7 +441,7 @@ impl Attrs {
match &*res.kind {
Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"),
Kind::Arg(_) | Kind::Flatten | Kind::ExternalSubcommand => res,
Kind::Arg(_, _) | Kind::Flatten | Kind::ExternalSubcommand => res,
}
}

Expand Down Expand Up @@ -512,7 +500,7 @@ impl Attrs {
);
}

let ty = Ty::from_syn_ty(&field.ty);
let (ty, _inner) = Ty::from_syn_ty(&field.ty);
match *ty {
Ty::OptionOption => {
abort!(
Expand All @@ -539,8 +527,9 @@ impl Attrs {
);
}
}
Kind::Arg(orig_ty) => {
let mut ty = Ty::from_syn_ty(&field.ty);
Kind::Arg(orig_ty, inner) => {
assert!(inner.is_empty());
let (mut ty, inner) = Ty::from_syn_ty(&field.ty);
if res.has_custom_parser {
match *ty {
Ty::Option | Ty::Vec | Ty::OptionVec => (),
Expand Down Expand Up @@ -596,15 +585,21 @@ impl Attrs {

_ => (),
}
res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());

// Serialize the `inner` type (eg. the `T` in `Vec<T>`) into
// tokens so that we can include it in macro expansions.
let mut inner_tokens = TokenStream::new();
inner.to_tokens(&mut inner_tokens);

res.kind = Sp::new(Kind::Arg(ty, inner_tokens), orig_ty.span());
}
}

res
}

fn set_kind(&mut self, kind: Sp<Kind>) {
if let Kind::Arg(_) = *self.kind {
if let Kind::Arg(_, _) = *self.kind {
self.kind = kind;
} else {
abort!(
Expand Down