rhg: Add support for --config CLI arguments
authorSimon Sapin <simon.sapin@octobus.net>
Mon, 08 Feb 2021 23:08:44 +0100
changeset 46504 2e5dd18d6dc3
parent 46503 d8730ff51d5a
child 46505 a25033eb43b5
rhg: Add support for --config CLI arguments Differential Revision: https://phab.mercurial-scm.org/D9971
rust/hg-core/src/config/config.rs
rust/hg-core/src/config/layer.rs
rust/rhg/src/main.rs
--- a/rust/hg-core/src/config/config.rs	Mon Feb 08 21:37:30 2021 +0100
+++ b/rust/hg-core/src/config/config.rs	Mon Feb 08 23:08:44 2021 +0100
@@ -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<Self, ConfigError> {
+    pub fn load(
+        cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
+    ) -> Result<Self, ConfigError> {
         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)
     }
 
--- a/rust/hg-core/src/config/layer.rs	Mon Feb 08 21:37:30 2021 +0100
+++ b/rust/hg-core/src/config/layer.rs	Mon Feb 08 23:08:44 2021 +0100
@@ -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<Item = impl AsRef<[u8]>>,
+    ) -> Result<Option<Self>, ConfigError> {
+        fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
+            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 {
--- a/rust/rhg/src/main.rs	Mon Feb 08 21:37:30 2021 +0100
+++ b/rust/rhg/src/main.rs	Mon Feb 08 23:08:44 2021 +0100
@@ -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)
     })();