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

Specify defaults in terms of the underlying type rather than strings #1694

Closed
emilazy opened this issue Feb 14, 2020 · 11 comments · Fixed by #2635
Closed

Specify defaults in terms of the underlying type rather than strings #1694

emilazy opened this issue Feb 14, 2020 · 11 comments · Fixed by #2635
Labels
A-derive Area: #[derive]` macro API
Milestone

Comments

@emilazy
Copy link
Contributor

emilazy commented Feb 14, 2020

I really like how the new clap v3 is shaping up! Now that structopt is integrated, it'd be great if defaults could be specified in terms of the default resulting value they produce rather than as a string. Other argument parsing libraries like Python's argparse work this way.

@pksunkara
Copy link
Member

Could you provide a small example so we are on the same page? Thanks

@emilazy
Copy link
Contributor Author

emilazy commented Feb 14, 2020

e.g.

enum Switch {
    Magic,
    MoreMagic,
}

impl FromStr for Switch {
    // ...
}

#[derive(Clap)]
struct Opts {
    #[clap(default_value(Switch::MoreMagic))]
    switch: Switch,
}

Currently you have to do e.g. #[clap(default_value("more-magic"))], which is less flexible and elegant (as well as incurring an unnecessary parse).

@pksunkara pksunkara added this to the 3.0 milestone Feb 14, 2020
@pksunkara pksunkara added the A-derive Area: #[derive]` macro API label Feb 14, 2020
@kbknapp
Copy link
Member

kbknapp commented Apr 29, 2020

If your enum implements Default you can simply use #[clap(default_value)] on that field.

Full example:

use std::fmt;

use clap::Clap;

#[derive(Clap, Debug, PartialEq, Copy, Clone)]
enum Magic {
    Little,
    Lots,
    None,
}

impl Default for Magic {
    fn default() -> Self {
        Magic::Little
    }
}

impl fmt::Display for Magic {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

#[derive(Clap)]
struct Ctx {
    /// How much magic do you want?
    #[clap(long, arg_enum, default_value)]
    magic: Magic,
}

fn main() {
    let ctx = Ctx::parse();
}

@kbknapp
Copy link
Member

kbknapp commented Apr 29, 2020

@emilazy it's not 100% what you asked for, but in your example it's at least part of the way there. I need to re-familiarize myself with the derive macros we made, but I think the underlying issue is that it just calls Arg::default_value underneath which only accepts a string. Now since we're already doing it where values have a valid default, we may be able to extend that case more generally...but I'm not 100% certain how much of a re-work that would entail.

@pksunkara
Copy link
Member

Implementation wise, this would just adding one more method to the ArgEnum trait.

@kbknapp
Copy link
Member

kbknapp commented Apr 29, 2020

We would also potentially need to impl Display for the enum as well...the downside being that prevents the consumer from doing this if they had some specialized version of Display they wanted to use.

@mainrs
Copy link

mainrs commented Jul 17, 2020

This would still be useful for cases where the underlying default of the application differs from the default of the type:

use clap::Clap;
use std::env;
use std::path::PathBuf;

#[derive(Clap, Debug)]
#[clap(author, version)]
pub struct App {
    #[clap(name = "REPO", default = "default_repo_path", parse(from_os_str))]
    pub repo_path: PathBuf,
}

fn default_repo_path() -> PathBuf {
    env::current_dir().expect("Failed to find current working directory!")
}

This is currently IIRC not possible. The default of PathBuf isn't useful here.

@Lunderberg
Copy link
Contributor

A similar change would also be useful for required_if's interaction with case insensitive arguments. Currently, I want to make a case-insensitive enum argument, where some of the enum values would also have additional required arguments. I have this set up as below, which works in most cases.

use clap::{arg_enum, App, Arg};                                                                                                                                      
                                                                                                                                                                     
arg_enum! {                                                                                                                                                          
    #[derive(Debug, PartialEq)]                                                                                                                                      
    enum EnumOpt {                                                                                                                                                   
        OptionA,                                                                                                                                                     
        OptionB,                                                                                                                                                     
    }                                                                                                                                                                
}                                                                                                                                                                    
                                                                                                                                                                     
fn main() {                                                                                                                                                          
    let matches = App::new("test")                                                                                                                                   
        .arg(                                                                                                                                                        
            Arg::with_name("enum")                                                                                                                                   
                .long("enum")                                                                                                                                        
                .takes_value(true)                                                                                                                                   
                .required(true)                                                                                                                                                      .possible_values(&EnumOpt::variants())                                                                                                               
                .case_insensitive(true),                                                                                                                             
        )                                                                                                                                                            
        .arg(                                                                                                                                                        
            Arg::with_name("dependent")                                                                                                                              
                .long("dependent")                                                                                                                                   
                .required_if("enum", "OptionB"),                                                                                                                     
        )                                                                                                                                                            
        .get_matches();                                                                                                                                              
    println!("Matches = {:?}", matches);                                                                                                                             
} 

If this is run as test --enum OptionA, I get the expected behavior, an error message that the --dependent flag is also required. However, if this is run as test --enum optiona, the enum value is correctly set, but there is no error message for the missing --dependent flag. Specifying required_if in terms of the underlying type would resolve this issue.

@epage
Copy link
Member

epage commented Jul 21, 2021

#2612 adds ArgEnum::as_arg(&self) -> &'static str to make it easier for end-users to implement Display so they can use clap(arg_enum, default_value). I've not yet gone through the effort of providing a Display or Default macro since there is probably more to be decided there.

@epage
Copy link
Member

epage commented Jul 21, 2021

Also wanted to add

Currently you have to do e.g. #[clap(default_value("more-magic"))], which is less flexible and elegant (as well as incurring an unnecessary parse).

We'll do that extra parsing anyways because we store the default in App and then pull it back out from ArgMatches, relying completely on the build APIs machinery to do things for us, like generate help text with the default.

@epage
Copy link
Member

epage commented Jul 22, 2021

Another way of solving this problem is we change the attributes to:

  • default_value: raw method that directly maps to Arg::default_value
  • default_value_t: takes in a type, defaults to Default::default

StructOpt porting work

  • If using default_value, change to default_value_t
  • If using default_value="foo", no change needed

Benefits

  • Keeps to the pattern of exposing the raw methods as-is (default_value just forwards to Arg::default_value)
  • Keeps to the pattern of typed stuff having a _t suffix (e.g. ArgMatches::value_of_t)
  • Makes magic method (non-raw) less magical by not having it do either typed or string, but instead each is strictly typed or string

epage added a commit to epage/clap that referenced this issue Jul 28, 2021
Right now
- `default_value="something"` is a raw method
- `default_value` uses native types

This commit splits the meanings
- `default_value="something"` is a raw method
- `default_value_t` uses `T::default()`
- `default_value_t=expr` uses an expression that evaluates to `T`

This is meant to mirror the `value_of` / `value_of_t` API.

At the moment, this is limited to `T: Display` to work with clap's
default system.  Something we can look at in the future is a way to
loosen that restriction.  One quick win is to specialize when `arg_enum`
is set.  The main downside is complicating the processing of attributes
because it then means we need some processed before others.

Since this builds on `clap`s existing default system, this also means
users do not get any performance gains out of using `default_value_t`,
since we still need to parse it but we also need to convert it to a
string.

Fixes clap-rs#1694
epage added a commit to epage/clap that referenced this issue Jul 28, 2021
Right now
- `default_value="something"` is a raw method
- `default_value` uses native types

This commit splits the meanings
- `default_value="something"` is a raw method
- `default_value_t` uses `T::default()`
- `default_value_t=expr` uses an expression that evaluates to `T`

This is meant to mirror the `value_of` / `value_of_t` API.

At the moment, this is limited to `T: Display` to work with clap's
default system.  Something we can look at in the future is a way to
loosen that restriction.  One quick win is to specialize when `arg_enum`
is set.  The main downside is complicating the processing of attributes
because it then means we need some processed before others.

Since this builds on `clap`s existing default system, this also means
users do not get any performance gains out of using `default_value_t`,
since we still need to parse it but we also need to convert it to a
string.

Fixes clap-rs#1694
epage added a commit to epage/clap that referenced this issue Jul 30, 2021
Right now
- `default_value="something"` is a raw method
- `default_value` uses native types

This commit splits the meanings
- `default_value="something"` is a raw method
- `default_value_t` uses `T::default()`
- `default_value_t=expr` uses an expression that evaluates to `T`

This is meant to mirror the `value_of` / `value_of_t` API.

At the moment, this is limited to `T: Display` to work with clap's
default system.  Something we can look at in the future is a way to
loosen that restriction.  One quick win is to specialize when `arg_enum`
is set.  The main downside is complicating the processing of attributes
because it then means we need some processed before others.

Since this builds on `clap`s existing default system, this also means
users do not get any performance gains out of using `default_value_t`,
since we still need to parse it but we also need to convert it to a
string.

Fixes clap-rs#1694
@pksunkara pksunkara modified the milestones: 3.0, 3.1 Aug 9, 2021
epage added a commit to epage/clap that referenced this issue Aug 13, 2021
Right now
- `default_value="something"` is a raw method
- `default_value` uses native types

This commit splits the meanings
- `default_value="something"` is a raw method
- `default_value_t` uses `T::default()`
- `default_value_t=expr` uses an expression that evaluates to `T`

This is meant to mirror the `value_of` / `value_of_t` API.

At the moment, this is limited to `T: Display` to work with clap's
default system.  Something we can look at in the future is a way to
loosen that restriction.  One quick win is to specialize when `arg_enum`
is set.  The main downside is complicating the processing of attributes
because it then means we need some processed before others.

Since this builds on `clap`s existing default system, this also means
users do not get any performance gains out of using `default_value_t`,
since we still need to parse it but we also need to convert it to a
string.

Fixes clap-rs#1694
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-derive Area: #[derive]` macro API
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants