Search code examples
rustclap

Idiomatic rust way to properly parse Clap ArgMatches


I'm learning rust and trying to make a find like utility (yes another one), im using clap and trying to support command line and config file for the program's parameters(this has nothing to do with the clap yml file).

Im trying to parse the commands and if no commands were passed to the app, i will try to load them from a config file. Now I don't know how to do this in an idiomatic way.

fn main() {
    let matches = App::new("findx")
        .version(crate_version!())
        .author(crate_authors!())
        .about("find + directory operations utility")
        .arg(
            Arg::with_name("paths")
               ...
        )
        .arg(
            Arg::with_name("patterns")
              ...
        )
        .arg(
            Arg::with_name("operation")
            ...
        )
        .get_matches();
    let paths;
    let patterns;
    let operation;
    if matches.is_present("patterns") && matches.is_present("operation") {
        patterns = matches.values_of("patterns").unwrap().collect();
        paths = matches.values_of("paths").unwrap_or(clap::Values<&str>{"./"}).collect(); // this doesn't work
        operation = match matches.value_of("operation").unwrap() { // I dont like this
            "Append" => Operation::Append,
            "Prepend" => Operation::Prepend,
            "Rename" => Operation::Rename,
            _ => {
                print!("Operation unsupported");
                process::exit(1);
            }
        };
    }else if Path::new("findx.yml").is_file(){
        //TODO: try load from config file
    }else{
        eprintln!("Command line parameters or findx.yml file must be provided");
        process::exit(1);
    }


    if let Err(e) = findx::run(Config {
        paths: paths,
        patterns: patterns,
        operation: operation,
    }) {
        eprintln!("Application error: {}", e);
        process::exit(1);
    }
}

There is an idiomatic way to extract Option and Result types values to the same scope, i mean all examples that i have read, uses match or if let Some(x) to consume the x value inside the scope of the pattern matching, but I need to assign the value to a variable.

Can someone help me with this, or point me to the right direction?

Best Regards


Solution

  • Personally I see nothing wrong with using the match statements and folding it or placing it in another function. But if you want to remove it there are many options.

    There is the ability to use the .default_value_if() method which is impl for clap::Arg and have a different default value depending on which match arm is matched.

    From the clap documentation

    //sets  value of arg "other" to "default" if value of "--opt" is "special"
    
    let m = App::new("prog")
        .arg(Arg::with_name("opt")
            .takes_value(true)
            .long("opt"))
        .arg(Arg::with_name("other")
            .long("other")
            .default_value_if("opt", Some("special"), "default"))
        .get_matches_from(vec![
            "prog", "--opt", "special"
        ]);
    
    assert_eq!(m.value_of("other"), Some("default"));
    

    In addition you can add a validator to your operation OR convert your valid operation values into flags.

    Here's an example converting your match arm values into individual flags (smaller example for clarity).

    extern crate clap;
    
    use clap::{Arg,App};
    
    fn command_line_interface<'a>() -> clap::ArgMatches<'a> {
        //Sets the command line interface of the program.
        App::new("something")
                .version("0.1")
                .arg(Arg::with_name("rename")
                     .help("renames something")
                     .short("r")
                     .long("rename"))
                .arg(Arg::with_name("prepend")
                     .help("prepends something")
                     .short("p")
                     .long("prepend"))
                .arg(Arg::with_name("append")
                     .help("appends something")
                     .short("a")
                     .long("append"))
                .get_matches()
    }
    
    #[derive(Debug)]
    enum Operation {
        Rename,
        Append,
        Prepend,
    }
    
    
    fn main() {
        let matches  = command_line_interface();
    
        let operation = if matches.is_present("rename") {
            Operation::Rename
        } else if matches.is_present("prepend"){
            Operation::Prepend
        } else {
            //DEFAULT
            Operation::Append
        };
        println!("Value of operation is {:?}",operation);
    }
    

    I hope this helps!

    EDIT:

    You can also use Subcommands with your specific operations. It all depends on what you want to interface to be like.

     let app_m = App::new("git")
         .subcommand(SubCommand::with_name("clone"))
         .subcommand(SubCommand::with_name("push"))
         .subcommand(SubCommand::with_name("commit"))
         .get_matches();
    
    match app_m.subcommand() {
        ("clone",  Some(sub_m)) => {}, // clone was used
        ("push",   Some(sub_m)) => {}, // push was used
        ("commit", Some(sub_m)) => {}, // commit was used
        _                       => {}, // Either no subcommand or one not tested for...
    }