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

Derive attribute: modify_fn(user_fn) where fn user_fn<T>(a: Arg) -> Arg generates user_fn::<T>(arg) for fields of type T #2990

Closed
wants to merge 1 commit 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
8 changes: 8 additions & 0 deletions clap_derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,14 @@ impl Attrs {

MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)),

ModifyFn(name, method_path) => self.push_method(name.clone(), match self.ty {
Some(ref t) => quote!(#method_path::<#t>),
None => abort!(
name,
"#[clap(modify_fn(..))] can be used only on field level",
),
}),

RenameAll(_, casing_lit) => {
self.casing = CasingStyle::from_lit(casing_lit);
}
Expand Down
12 changes: 11 additions & 1 deletion clap_derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use syn::{
self, parenthesized,
parse::{Parse, ParseBuffer, ParseStream},
punctuated::Punctuated,
Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token,
Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token, Path,
};

#[allow(clippy::large_enum_variant)]
Expand Down Expand Up @@ -45,6 +45,9 @@ pub enum ClapAttr {

// ident(arbitrary_expr,*)
MethodCall(Ident, Vec<Expr>),

// ident(path)
ModifyFn(Ident, Path),
}

impl Parse for ClapAttr {
Expand Down Expand Up @@ -165,6 +168,13 @@ impl Parse for ClapAttr {
}
},

"modify_fn" => match nested.parse::<Path>() {
Ok(method_path) => Ok(ModifyFn(name, method_path)),
Err(_) => abort!(name,
"`#[clap(method_t(...))` must contain one identifier"
),
},

_ => {
let method_args: Punctuated<_, Token![,]> =
nested.parse_terminated(Expr::parse)?;
Expand Down
36 changes: 36 additions & 0 deletions clap_derive/tests/modify_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use clap::{Arg, Parser};

mod utils;

use utils::*;

#[test]
fn modify_fn_default_value_t() {
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(modify_fn(clap::default_value_t))]
arg: i32,
}
assert_eq!(Opt { arg: 0 }, Opt::try_parse_from(&["test"]).unwrap());
assert_eq!(Opt { arg: 1 }, Opt::try_parse_from(&["test", "1"]).unwrap());

let help = get_long_help::<Opt>();
assert!(help.contains("[default: 0]"));
}

#[test]
fn modify_fn_generate_about() {
const MY_ABOUT: &str = "This could be generated";
fn generate_about_and_def<T: ToString + Default>(arg: Arg) -> Arg {
arg.about(MY_ABOUT)
.modify_fn(clap::default_value_t::<T>)
}
#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[clap(modify_fn(generate_about_and_def))]
arg: String,
}
let help = get_long_help::<Opt>();
assert!(help.contains(MY_ABOUT));
assert!(help.contains("[default: ]"));
}
21 changes: 21 additions & 0 deletions src/build/arg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4929,6 +4929,27 @@ impl<'help> Arg<'help> {
pub(crate) fn is_multiple(&self) -> bool {
self.is_set(ArgSettings::MultipleValues) | self.is_set(ArgSettings::MultipleOccurrences)
}

#[inline]
/// Allows adding free functions to a chain
///
/// ```
/// # use clap::{Arg, ValueHint};
/// let func = |a| a;
///
/// // Instead of this
/// let mut arg = Arg::new("test");
/// arg = func(arg);
///
/// // You can do this
/// let arg = Arg::new("test").modify_fn(func);
/// ```
pub fn modify_fn<F>(self, mod_fn: F) -> Self
where
F: Fn(Self) -> Self
{
(mod_fn)(self)
}
}

#[cfg(feature = "yaml")]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub use crate::{
},
parse::errors::{Error, ErrorKind, Result},
parse::{ArgMatches, Indices, OsValues, Values},
util::color::ColorChoice,
util::{color::ColorChoice, modify_fn::default_value_t},
};

pub use crate::derive::{ArgEnum, Args, FromArgMatches, IntoApp, Parser, Subcommand};
Expand Down
1 change: 1 addition & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod graph;
mod id;
#[cfg(feature = "env")]
mod str_to_bool;
pub(crate) mod modify_fn;

pub use self::fnv::Key;

Expand Down
18 changes: 18 additions & 0 deletions src/util/modify_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::Arg;

/// Adds default_value which is obtained via
/// Default and ToString traits
pub fn default_value_t<T>(arg: Arg) -> Arg
where
T: Default + ToString
{
let s = make_static_str(<T as Default>::default());
arg.default_value(s).required(false)
}

fn make_static_str<T>(t: T) -> &'static str
where
T: ToString
{
Box::leak(t.to_string().into_boxed_str())
}