I'm programming a CLI using clap to parse my arguments. I want to provide defaults for options, but if there's a config file, the config file should win against defaults.
It's easy to prioritize command line arguments over defaults, but I want the priority order of:
If the config file isn't set by the command line options, it's also easy to set that up, just by parsing the config file before running parse_args
, and supplying the values from the parsed config file into default_value
. The problem is that if you specify the config file in the command line, you can't change the defaults until after the parsing.
The only way I can think of doing it is by not setting a default_value
and then manually match ""
in value_of
. The issue is that in that case, clap won't be able to build a useful --help
.
Is there a way to get clap to read the config file itself?
From clap's documentation on default_value
:
NOTE: If the user does not use this argument at runtime
ArgMatches::is_present
will still return true. If you wish to determine whether the argument was used at runtime or not, considerArgMatches::occurrences_of
which will return0
if the argument was not used at runtime.https://docs.rs/clap/2.32.0/clap/struct.Arg.html#method.default_value
This can be utilized to get the behavior you described:
extern crate clap;
use clap::{App, Arg};
use std::fs::File;
use std::io::prelude::*;
fn main() {
let matches = App::new("MyApp")
.version("0.1.0")
.about("Example for StackOverflow")
.arg(
Arg::with_name("config")
.short("c")
.long("config")
.value_name("FILE")
.help("Sets a custom config file"),
)
.arg(
Arg::with_name("example")
.short("e")
.long("example")
.help("Sets an example parameter")
.default_value("default_value")
.takes_value(true),
)
.get_matches();
let mut value = String::new();
if let Some(c) = matches.value_of("config") {
let file = File::open(c);
match file {
Ok(mut f) => {
// Note: I have a file `config.txt` that has contents `file_value`
f.read_to_string(&mut value).expect("Error reading value");
}
Err(_) => println!("Error reading file"),
}
// Note: this lets us override the config file value with the
// cli argument, if provided
if matches.occurrences_of("example") > 0 {
value = matches.value_of("example").unwrap().to_string();
}
} else {
value = matches.value_of("example").unwrap().to_string();
}
println!("Value for config: {}", value);
}
// Code above licensed CC0
// https://creativecommons.org/share-your-work/public-domain/cc0/
Resulting in the behavior:
./target/debug/example
Value for config: default_value
./target/debug/example --example cli_value
Value for config: cli_value
./target/debug/example --config config.txt
Value for config: file_value
./target/debug/example --example cli_value --config config.txt
Value for config: cli_value