diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -65,9 +65,9 @@ /// Load system and user configuration from various files. /// /// This is also affected by some environment variables. - /// - /// TODO: add a parameter for `--config` CLI arguments - pub fn load() -> Result { + pub fn load( + cli_config_args: impl IntoIterator>, + ) -> Result { let mut config = Self { layers: Vec::new() }; let opt_rc_path = env::var_os("HGRCPATH"); // HGRCPATH replaces system config @@ -92,6 +92,9 @@ } } } + if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? { + config.layers.push(layer) + } Ok(config) } diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -51,6 +51,49 @@ } } + /// Parse `--config` CLI arguments and return a layer if there’s any + pub(crate) fn parse_cli_args( + cli_config_args: impl IntoIterator>, + ) -> Result, ConfigError> { + fn parse_one(arg: &[u8]) -> Option<(Vec, Vec, Vec)> { + use crate::utils::SliceExt; + + let (section_and_item, value) = split_2(arg, b'=')?; + let (section, item) = split_2(section_and_item.trim(), b'.')?; + Some(( + section.to_owned(), + item.to_owned(), + value.trim().to_owned(), + )) + } + + fn split_2(bytes: &[u8], separator: u8) -> Option<(&[u8], &[u8])> { + let mut iter = bytes.splitn(2, |&byte| byte == separator); + let a = iter.next()?; + let b = iter.next()?; + Some((a, b)) + } + + let mut layer = Self::new(ConfigOrigin::CommandLine); + for arg in cli_config_args { + let arg = arg.as_ref(); + if let Some((section, item, value)) = parse_one(arg) { + layer.add(section, item, value, None); + } else { + Err(HgError::abort(format!( + "malformed --config option: \"{}\" \ + (use --config section.name=value)", + String::from_utf8_lossy(arg), + )))? + } + } + if layer.sections.is_empty() { + Ok(None) + } else { + Ok(Some(layer)) + } + } + /// Returns whether this layer comes from `--config` CLI arguments pub(crate) fn is_from_command_line(&self) -> bool { if let ConfigOrigin::CommandLine = self.origin { diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -20,6 +20,17 @@ .value_name("REPO") .takes_value(true), ) + .arg( + Arg::with_name("config") + .help("set/override config option (use 'section.name=value')") + .long("--config") + .value_name("CONFIG") + .takes_value(true) + // Ok: `--config section.key1=val --config section.key2=val2` + .multiple(true) + // Not ok: `--config section.key1=val section.key2=val2` + .number_of_values(1), + ) } fn main() { @@ -47,12 +58,22 @@ // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s. // `hg log -R ./foo` - let global_arg = + let value_of_global_arg = |name| args.value_of_os(name).or_else(|| matches.value_of_os(name)); + // For arguments where multiple occurences are allowed, return a + // possibly-iterator of all values. + let values_of_global_arg = |name: &str| { + let a = matches.values_of_os(name).into_iter().flatten(); + let b = args.values_of_os(name).into_iter().flatten(); + a.chain(b) + }; - let repo_path = global_arg("repository").map(Path::new); + let repo_path = value_of_global_arg("repository").map(Path::new); let result = (|| -> Result<(), CommandError> { - let config = hg::config::Config::load()?; + let config_args = values_of_global_arg("config") + // `get_bytes_from_path` works for OsStr the same as for Path + .map(hg::utils::files::get_bytes_from_path); + let config = hg::config::Config::load(config_args)?; run(&ui, &config, repo_path, args) })();