Search code examples
rustclap

Clap - default value for PathBuf


How can I set the default value for clap PathBuf argument? Is it even doable? I keep bumping from one error to another.

When I try doing it the "Struct" way:

use std::{env, path::PathBuf};
use clap::{Parser, Args};

fn get_default_log_path() -> PathBuf {
    let mut path = env::current_exe().unwrap();
    path.pop();
    path.push("log/debug.log");
    path
}

#[derive(Parser, Debug)]
struct Cli {
  #[arg(default_value_t=get_default_log_path())]
  log_path: PathBuf
}

fn main() {
  let args = Cli::parse();
  println!("{:?}", args);
}

I get: std::path::PathBuf doesn't implement std::fmt::Display...

When I try it with string:

#[arg(default_value = get_default_log_path().as_os_str())]
log_path: PathBuf

I get: temporary value dropped while borrowed

And if I do .to_owned() on that it outputs: the trait bound clap::builder::OsStr: From<OsString> is not satisfied

Is this even possible or am I doing something wrong? I know it can be done with Option<PathBuf> and deal with it in main function. But can this be done this way or nah?


Solution

  • Due to the fact that the default values get printed in the help (--help/-h) page, they have to implement Display.

    PathBuf sadly doesn't do so, so you can't use default_value_t to provide a default for it. You can, however, use default_value without a problem.

    The next problem you will face is that by default, clap only supports static string references for default values. To enable dynamic, owned values for defaults, you need to enable the string feature of clap in your Cargo.toml:

    clap = { version = "4.3.0", features = ["derive", "string"] }
    

    Then, the following works:

    use clap::Parser;
    use std::{env, path::PathBuf};
    
    fn get_default_log_path() -> PathBuf {
        let mut path = env::current_exe().unwrap();
        path.pop();
        path.push("log/debug.log");
        path
    }
    
    #[derive(Parser, Debug)]
    struct Cli {
        #[arg(default_value=get_default_log_path().into_os_string())]
        log_path: PathBuf,
    }
    
    fn main() {
        let args = Cli::parse();
        println!("{:?}", args);
    }
    
    $ cargo build --release
    ...
    
    $ target\release\rust-tmp.exe
    Cli { log_path: "D:\\Projects\\rust-tmp\\target\\release\\log/debug.log" }
    
    $ target\release\rust-tmp.exe --help
    Cli { log_path: "D:\\Projects\\rust-tmp\\target\\release\\log/debug.log" }
    
    D:\Projects\rust-tmp>target\release\rust-tmp.exe --help
    Usage: rust-tmp.exe [LOG_PATH]
    
    Arguments:
      [LOG_PATH]  [default: D:\Projects\rust-tmp\target\release\log/debug.log]
    
    Options:
      -h, --help  Print help
    

    Be aware of the mixed / and \, though. Don't use / in a PathBuf - use multiple push()s instead. Like this:

    use clap::Parser;
    use std::{env, path::PathBuf};
    
    fn get_default_log_path() -> PathBuf {
        let mut path = env::current_exe().unwrap();
        path.pop();
        path.push("log");
        path.push("debug.log");
        path
    }
    
    #[derive(Parser, Debug)]
    struct Cli {
        #[arg(default_value=get_default_log_path().into_os_string())]
        log_path: PathBuf,
    }
    
    fn main() {
        let args = Cli::parse();
        println!("{:?}", args);
    }
    
    Cli { log_path: "D:\\Projects\\rust-tmp\\target\\release\\log\\debug.log" }