--- a/rust/Cargo.lock Mon Jan 23 18:08:11 2023 +0100
+++ b/rust/Cargo.lock Thu Jul 06 14:32:07 2023 +0200
@@ -476,6 +476,12 @@
[[package]]
name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
@@ -517,7 +523,7 @@
"derive_more",
"flate2",
"format-bytes",
- "hashbrown",
+ "hashbrown 0.13.1",
"home",
"im-rc",
"itertools",
@@ -535,9 +541,11 @@
"regex",
"same-file",
"self_cell",
+ "serde",
"sha-1 0.10.0",
"tempfile",
"thread_local",
+ "toml",
"twox-hash",
"zstd",
]
@@ -610,6 +618,16 @@
]
[[package]]
+name = "indexmap"
+version = "1.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -749,6 +767,15 @@
]
[[package]]
+name = "nom8"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1107,6 +1134,35 @@
checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
[[package]]
+name = "serde"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "sha-1"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1160,9 +1216,9 @@
[[package]]
name = "syn"
-version = "1.0.103"
+version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
@@ -1213,6 +1269,40 @@
]
[[package]]
+name = "toml"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
+dependencies = [
+ "indexmap",
+ "nom8",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+]
+
+[[package]]
name = "twox-hash"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hg-core/src/config/config_items.rs Thu Jul 06 14:32:07 2023 +0200
@@ -0,0 +1,669 @@
+//! Code for parsing default Mercurial config items.
+use itertools::Itertools;
+use serde::Deserialize;
+
+use crate::{errors::HgError, exit_codes, FastHashMap};
+
+/// Corresponds to the structure of `mercurial/configitems.toml`.
+#[derive(Debug, Deserialize)]
+pub struct ConfigItems {
+ items: Vec<DefaultConfigItem>,
+ templates: FastHashMap<String, Vec<TemplateItem>>,
+ #[serde(rename = "template-applications")]
+ template_applications: Vec<TemplateApplication>,
+}
+
+/// Corresponds to a config item declaration in `mercurial/configitems.toml`.
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[serde(try_from = "RawDefaultConfigItem")]
+pub struct DefaultConfigItem {
+ /// Section of the config the item is in (e.g. `[merge-tools]`)
+ section: String,
+ /// Name of the item (e.g. `meld.gui`)
+ name: String,
+ /// Default value (can be dynamic, see [`DefaultConfigItemType`])
+ default: Option<DefaultConfigItemType>,
+ /// If the config option is generic (e.g. `merge-tools.*`), defines
+ /// the priority of this item relative to other generic items.
+ /// If we're looking for <pattern>, then all generic items within the same
+ /// section will be sorted by order of priority, and the first regex match
+ /// against `name` is returned.
+ #[serde(default)]
+ priority: Option<isize>,
+ /// Aliases, if any. Each alias is a tuple of `(section, name)` for each
+ /// option that is aliased to this one.
+ #[serde(default)]
+ alias: Vec<(String, String)>,
+ /// Whether the config item is marked as experimental
+ #[serde(default)]
+ experimental: bool,
+ /// The (possibly empty) docstring for the item
+ #[serde(default)]
+ documentation: String,
+}
+
+/// Corresponds to the raw (i.e. on disk) structure of config items. Used as
+/// an intermediate step in deserialization.
+#[derive(Clone, Debug, Deserialize)]
+struct RawDefaultConfigItem {
+ section: String,
+ name: String,
+ default: Option<toml::Value>,
+ #[serde(rename = "default-type")]
+ default_type: Option<String>,
+ #[serde(default)]
+ priority: isize,
+ #[serde(default)]
+ generic: bool,
+ #[serde(default)]
+ alias: Vec<(String, String)>,
+ #[serde(default)]
+ experimental: bool,
+ #[serde(default)]
+ documentation: String,
+}
+
+impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem {
+ type Error = HgError;
+
+ fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> {
+ Ok(Self {
+ section: value.section,
+ name: value.name,
+ default: raw_default_to_concrete(
+ value.default_type,
+ value.default,
+ )?,
+ priority: if value.generic {
+ Some(value.priority)
+ } else {
+ None
+ },
+ alias: value.alias,
+ experimental: value.experimental,
+ documentation: value.documentation,
+ })
+ }
+}
+
+impl DefaultConfigItem {
+ fn is_generic(&self) -> bool {
+ self.priority.is_some()
+ }
+}
+
+impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> {
+ type Error = HgError;
+
+ fn try_from(
+ value: &'a DefaultConfigItem,
+ ) -> Result<Option<&'a str>, Self::Error> {
+ match &value.default {
+ Some(default) => {
+ let err = HgError::abort(
+ format!(
+ "programming error: wrong query on config item '{}.{}'",
+ value.section,
+ value.name
+ ),
+ exit_codes::ABORT,
+ Some(format!(
+ "asked for '&str', type of default is '{}'",
+ default.type_str()
+ )),
+ );
+ match default {
+ DefaultConfigItemType::Primitive(toml::Value::String(
+ s,
+ )) => Ok(Some(s)),
+ _ => Err(err),
+ }
+ }
+ None => Ok(None),
+ }
+ }
+}
+
+impl TryFrom<&DefaultConfigItem> for Option<bool> {
+ type Error = HgError;
+
+ fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
+ match &value.default {
+ Some(default) => {
+ let err = HgError::abort(
+ format!(
+ "programming error: wrong query on config item '{}.{}'",
+ value.section,
+ value.name
+ ),
+ exit_codes::ABORT,
+ Some(format!(
+ "asked for 'bool', type of default is '{}'",
+ default.type_str()
+ )),
+ );
+ match default {
+ DefaultConfigItemType::Primitive(
+ toml::Value::Boolean(b),
+ ) => Ok(Some(*b)),
+ _ => Err(err),
+ }
+ }
+ None => Ok(Some(false)),
+ }
+ }
+}
+
+impl TryFrom<&DefaultConfigItem> for Option<u32> {
+ type Error = HgError;
+
+ fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
+ match &value.default {
+ Some(default) => {
+ let err = HgError::abort(
+ format!(
+ "programming error: wrong query on config item '{}.{}'",
+ value.section,
+ value.name
+ ),
+ exit_codes::ABORT,
+ Some(format!(
+ "asked for 'u32', type of default is '{}'",
+ default.type_str()
+ )),
+ );
+ match default {
+ DefaultConfigItemType::Primitive(
+ toml::Value::Integer(b),
+ ) => {
+ Ok(Some((*b).try_into().expect("TOML integer to u32")))
+ }
+ _ => Err(err),
+ }
+ }
+ None => Ok(None),
+ }
+ }
+}
+
+impl TryFrom<&DefaultConfigItem> for Option<u64> {
+ type Error = HgError;
+
+ fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
+ match &value.default {
+ Some(default) => {
+ let err = HgError::abort(
+ format!(
+ "programming error: wrong query on config item '{}.{}'",
+ value.section,
+ value.name
+ ),
+ exit_codes::ABORT,
+ Some(format!(
+ "asked for 'u64', type of default is '{}'",
+ default.type_str()
+ )),
+ );
+ match default {
+ DefaultConfigItemType::Primitive(
+ toml::Value::Integer(b),
+ ) => {
+ Ok(Some((*b).try_into().expect("TOML integer to u64")))
+ }
+ _ => Err(err),
+ }
+ }
+ None => Ok(None),
+ }
+ }
+}
+
+/// Allows abstracting over more complex default values than just primitives.
+/// The former `configitems.py` contained some dynamic code that is encoded
+/// in this enum.
+#[derive(Debug, PartialEq, Clone, Deserialize)]
+pub enum DefaultConfigItemType {
+ /// Some primitive type (string, integer, boolean)
+ Primitive(toml::Value),
+ /// A dynamic value that will be given by the code at runtime
+ Dynamic,
+ /// An lazily-returned array (possibly only relevant in the Python impl)
+ /// Example: `lambda: [b"zstd", b"zlib"]`
+ Lambda(Vec<String>),
+ /// For now, a special case for `web.encoding` that points to the
+ /// `encoding.encoding` module in the Python impl so that local encoding
+ /// is correctly resolved at runtime
+ LazyModule(String),
+ ListType,
+}
+
+impl DefaultConfigItemType {
+ pub fn type_str(&self) -> &str {
+ match self {
+ DefaultConfigItemType::Primitive(primitive) => {
+ primitive.type_str()
+ }
+ DefaultConfigItemType::Dynamic => "dynamic",
+ DefaultConfigItemType::Lambda(_) => "lambda",
+ DefaultConfigItemType::LazyModule(_) => "lazy_module",
+ DefaultConfigItemType::ListType => "list_type",
+ }
+ }
+}
+
+/// Most of the fields are shared with [`DefaultConfigItem`].
+#[derive(Debug, Clone, Deserialize)]
+#[serde(try_from = "RawTemplateItem")]
+struct TemplateItem {
+ suffix: String,
+ default: Option<DefaultConfigItemType>,
+ priority: Option<isize>,
+ #[serde(default)]
+ alias: Vec<(String, String)>,
+ #[serde(default)]
+ experimental: bool,
+ #[serde(default)]
+ documentation: String,
+}
+
+/// Corresponds to the raw (i.e. on disk) representation of a template item.
+/// Used as an intermediate step in deserialization.
+#[derive(Clone, Debug, Deserialize)]
+struct RawTemplateItem {
+ suffix: String,
+ default: Option<toml::Value>,
+ #[serde(rename = "default-type")]
+ default_type: Option<String>,
+ #[serde(default)]
+ priority: isize,
+ #[serde(default)]
+ generic: bool,
+ #[serde(default)]
+ alias: Vec<(String, String)>,
+ #[serde(default)]
+ experimental: bool,
+ #[serde(default)]
+ documentation: String,
+}
+
+impl TemplateItem {
+ fn into_default_item(
+ self,
+ application: TemplateApplication,
+ ) -> DefaultConfigItem {
+ DefaultConfigItem {
+ section: application.section,
+ name: application
+ .prefix
+ .map(|prefix| format!("{}.{}", prefix, self.suffix))
+ .unwrap_or(self.suffix),
+ default: self.default,
+ priority: self.priority,
+ alias: self.alias,
+ experimental: self.experimental,
+ documentation: self.documentation,
+ }
+ }
+}
+
+impl TryFrom<RawTemplateItem> for TemplateItem {
+ type Error = HgError;
+
+ fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> {
+ Ok(Self {
+ suffix: value.suffix,
+ default: raw_default_to_concrete(
+ value.default_type,
+ value.default,
+ )?,
+ priority: if value.generic {
+ Some(value.priority)
+ } else {
+ None
+ },
+ alias: value.alias,
+ experimental: value.experimental,
+ documentation: value.documentation,
+ })
+ }
+}
+
+/// Transforms the on-disk string-based representation of complex default types
+/// to the concrete [`DefaultconfigItemType`].
+fn raw_default_to_concrete(
+ default_type: Option<String>,
+ default: Option<toml::Value>,
+) -> Result<Option<DefaultConfigItemType>, HgError> {
+ Ok(match default_type.as_deref() {
+ None => default.as_ref().map(|default| {
+ DefaultConfigItemType::Primitive(default.to_owned())
+ }),
+ Some("dynamic") => Some(DefaultConfigItemType::Dynamic),
+ Some("list_type") => Some(DefaultConfigItemType::ListType),
+ Some("lambda") => match &default {
+ Some(default) => Some(DefaultConfigItemType::Lambda(
+ default.to_owned().try_into().map_err(|e| {
+ HgError::abort(
+ e.to_string(),
+ exit_codes::ABORT,
+ Some("Check 'mercurial/configitems.toml'".into()),
+ )
+ })?,
+ )),
+ None => {
+ return Err(HgError::abort(
+ "lambda defined with no return value".to_string(),
+ exit_codes::ABORT,
+ Some("Check 'mercurial/configitems.toml'".into()),
+ ))
+ }
+ },
+ Some("lazy_module") => match &default {
+ Some(default) => {
+ Some(DefaultConfigItemType::LazyModule(match default {
+ toml::Value::String(module) => module.to_owned(),
+ _ => {
+ return Err(HgError::abort(
+ "lazy_module module name should be a string"
+ .to_string(),
+ exit_codes::ABORT,
+ Some("Check 'mercurial/configitems.toml'".into()),
+ ))
+ }
+ }))
+ }
+ None => {
+ return Err(HgError::abort(
+ "lazy_module should have a default value".to_string(),
+ exit_codes::ABORT,
+ Some("Check 'mercurial/configitems.toml'".into()),
+ ))
+ }
+ },
+ Some(invalid) => {
+ return Err(HgError::abort(
+ format!("invalid default_type '{}'", invalid),
+ exit_codes::ABORT,
+ Some("Check 'mercurial/configitems.toml'".into()),
+ ))
+ }
+ })
+}
+
+#[derive(Debug, Clone, Deserialize)]
+struct TemplateApplication {
+ template: String,
+ section: String,
+ #[serde(default)]
+ prefix: Option<String>,
+}
+
+/// Represents the (dynamic) set of default core Mercurial config items from
+/// `mercurial/configitems.toml`.
+#[derive(Clone, Debug, Default)]
+pub struct DefaultConfig {
+ /// Mapping of section -> (mapping of name -> item)
+ items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>,
+}
+
+impl DefaultConfig {
+ pub fn empty() -> DefaultConfig {
+ Self {
+ items: Default::default(),
+ }
+ }
+
+ /// Returns `Self`, given the contents of `mercurial/configitems.toml`
+ #[logging_timer::time("trace")]
+ pub fn from_contents(contents: &str) -> Result<Self, HgError> {
+ let mut from_file: ConfigItems =
+ toml::from_str(contents).map_err(|e| {
+ HgError::abort(
+ e.to_string(),
+ exit_codes::ABORT,
+ Some("Check 'mercurial/configitems.toml'".into()),
+ )
+ })?;
+
+ let mut flat_items = from_file.items;
+
+ for application in from_file.template_applications.drain(..) {
+ match from_file.templates.get(&application.template) {
+ None => return Err(
+ HgError::abort(
+ format!(
+ "template application refers to undefined template '{}'",
+ application.template
+ ),
+ exit_codes::ABORT,
+ Some("Check 'mercurial/configitems.toml'".into())
+ )
+ ),
+ Some(template_items) => {
+ for template_item in template_items {
+ flat_items.push(
+ template_item
+ .clone()
+ .into_default_item(application.clone()),
+ )
+ }
+ }
+ };
+ }
+
+ let items = flat_items.into_iter().fold(
+ FastHashMap::default(),
+ |mut acc, item| {
+ acc.entry(item.section.to_owned())
+ .or_insert_with(|| {
+ let mut section = FastHashMap::default();
+ section.insert(item.name.to_owned(), item.to_owned());
+ section
+ })
+ .insert(item.name.to_owned(), item);
+ acc
+ },
+ );
+
+ Ok(Self { items })
+ }
+
+ /// Return the default config item that matches `section` and `item`.
+ pub fn get(
+ &self,
+ section: &[u8],
+ item: &[u8],
+ ) -> Option<&DefaultConfigItem> {
+ // Core items must be valid UTF-8
+ let section = String::from_utf8_lossy(section);
+ let section_map = self.items.get(section.as_ref())?;
+ let item_name_lossy = String::from_utf8_lossy(item);
+ match section_map.get(item_name_lossy.as_ref()) {
+ Some(item) => Some(item),
+ None => {
+ for generic_item in section_map
+ .values()
+ .filter(|item| item.is_generic())
+ .sorted_by_key(|item| match item.priority {
+ Some(priority) => (priority, &item.name),
+ _ => unreachable!(),
+ })
+ {
+ if regex::bytes::Regex::new(&generic_item.name)
+ .expect("invalid regex in configitems")
+ .is_match(item)
+ {
+ return Some(generic_item);
+ }
+ }
+ None
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::config::config_items::{
+ DefaultConfigItem, DefaultConfigItemType,
+ };
+
+ use super::DefaultConfig;
+
+ #[test]
+ fn test_config_read() {
+ let contents = r#"
+[[items]]
+section = "alias"
+name = "abcd.*"
+default = 3
+generic = true
+priority = -1
+
+[[items]]
+section = "alias"
+name = ".*"
+default-type = "dynamic"
+generic = true
+
+[[items]]
+section = "cmdserver"
+name = "track-log"
+default-type = "lambda"
+default = [ "chgserver", "cmdserver", "repocache",]
+
+[[items]]
+section = "chgserver"
+name = "idletimeout"
+default = 3600
+
+[[items]]
+section = "cmdserver"
+name = "message-encodings"
+default-type = "list_type"
+
+[[items]]
+section = "web"
+name = "encoding"
+default-type = "lazy_module"
+default = "encoding.encoding"
+
+[[items]]
+section = "command-templates"
+name = "graphnode"
+alias = [["ui", "graphnodetemplate"]]
+documentation = """This is a docstring.
+This is another line \
+but this is not."""
+
+[[items]]
+section = "censor"
+name = "policy"
+default = "abort"
+experimental = true
+
+[[template-applications]]
+template = "diff-options"
+section = "commands"
+prefix = "revert.interactive"
+
+[[template-applications]]
+template = "diff-options"
+section = "diff"
+
+[templates]
+[[templates.diff-options]]
+suffix = "nodates"
+default = false
+
+[[templates.diff-options]]
+suffix = "showfunc"
+default = false
+
+[[templates.diff-options]]
+suffix = "unified"
+"#;
+ let res = DefaultConfig::from_contents(contents);
+ let config = match res {
+ Ok(config) => config,
+ Err(e) => panic!("{}", e),
+ };
+ let expected = DefaultConfigItem {
+ section: "censor".into(),
+ name: "policy".into(),
+ default: Some(DefaultConfigItemType::Primitive("abort".into())),
+ priority: None,
+ alias: vec![],
+ experimental: true,
+ documentation: "".into(),
+ };
+ assert_eq!(config.get(b"censor", b"policy"), Some(&expected));
+
+ // Test generic priority. The `.*` pattern is wider than `abcd.*`, but
+ // `abcd.*` has priority, so it should match first.
+ let expected = DefaultConfigItem {
+ section: "alias".into(),
+ name: "abcd.*".into(),
+ default: Some(DefaultConfigItemType::Primitive(3.into())),
+ priority: Some(-1),
+ alias: vec![],
+ experimental: false,
+ documentation: "".into(),
+ };
+ assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected));
+
+ //... but if it doesn't, we should fallback to `.*`
+ let expected = DefaultConfigItem {
+ section: "alias".into(),
+ name: ".*".into(),
+ default: Some(DefaultConfigItemType::Dynamic),
+ priority: Some(0),
+ alias: vec![],
+ experimental: false,
+ documentation: "".into(),
+ };
+ assert_eq!(config.get(b"alias", b"something"), Some(&expected));
+
+ let expected = DefaultConfigItem {
+ section: "chgserver".into(),
+ name: "idletimeout".into(),
+ default: Some(DefaultConfigItemType::Primitive(3600.into())),
+ priority: None,
+ alias: vec![],
+ experimental: false,
+ documentation: "".into(),
+ };
+ assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected));
+
+ let expected = DefaultConfigItem {
+ section: "cmdserver".into(),
+ name: "track-log".into(),
+ default: Some(DefaultConfigItemType::Lambda(vec![
+ "chgserver".into(),
+ "cmdserver".into(),
+ "repocache".into(),
+ ])),
+ priority: None,
+ alias: vec![],
+ experimental: false,
+ documentation: "".into(),
+ };
+ assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected));
+
+ let expected = DefaultConfigItem {
+ section: "command-templates".into(),
+ name: "graphnode".into(),
+ default: None,
+ priority: None,
+ alias: vec![("ui".into(), "graphnodetemplate".into())],
+ experimental: false,
+ documentation:
+ "This is a docstring.\nThis is another line but this is not."
+ .into(),
+ };
+ assert_eq!(
+ config.get(b"command-templates", b"graphnode"),
+ Some(&expected)
+ );
+ }
+}
--- a/rust/hg-core/src/config/mod.rs Mon Jan 23 18:08:11 2023 +0100
+++ b/rust/hg-core/src/config/mod.rs Thu Jul 06 14:32:07 2023 +0200
@@ -9,14 +9,19 @@
//! Mercurial config parsing and interfaces.
+pub mod config_items;
mod layer;
mod plain_info;
mod values;
pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
+use lazy_static::lazy_static;
pub use plain_info::PlainInfo;
+use self::config_items::DefaultConfig;
+use self::config_items::DefaultConfigItem;
use self::layer::ConfigLayer;
use self::layer::ConfigValue;
+use crate::errors::HgError;
use crate::errors::{HgResultExt, IoResultExt};
use crate::utils::files::get_bytes_from_os_str;
use format_bytes::{write_bytes, DisplayBytes};
@@ -26,6 +31,14 @@
use std::path::{Path, PathBuf};
use std::str;
+lazy_static! {
+ static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = {
+ DefaultConfig::from_contents(include_str!(
+ "../../../../mercurial/configitems.toml"
+ ))
+ };
+}
+
/// Holds the config values for the current repository
/// TODO update this docstring once we support more sources
#[derive(Clone)]
@@ -347,13 +360,32 @@
self.plain = plain;
}
+ /// Returns the default value for the given config item, if any.
+ pub fn get_default(
+ &self,
+ section: &[u8],
+ item: &[u8],
+ ) -> Result<Option<&DefaultConfigItem>, HgError> {
+ let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| {
+ HgError::abort(
+ e.to_string(),
+ crate::exit_codes::ABORT,
+ Some("`mercurial/configitems.toml` is not valid".into()),
+ )
+ })?;
+ Ok(default_config.get(section, item))
+ }
+
fn get_parse<'config, T: 'config>(
&'config self,
section: &[u8],
item: &[u8],
expected_type: &'static str,
parse: impl Fn(&'config [u8]) -> Option<T>,
- ) -> Result<Option<T>, ConfigValueParseError> {
+ ) -> Result<Option<T>, HgError>
+ where
+ Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
+ {
match self.get_inner(section, item) {
Some((layer, v)) => match parse(&v.bytes) {
Some(b) => Ok(Some(b)),
@@ -364,9 +396,15 @@
section: section.to_owned(),
item: item.to_owned(),
expected_type,
- })),
+ })
+ .into()),
},
- None => Ok(None),
+ None => match self.get_default(section, item)? {
+ Some(default) => Ok(default.try_into()?),
+ None => {
+ Ok(None)
+ }
+ },
}
}
@@ -376,7 +414,7 @@
&self,
section: &[u8],
item: &[u8],
- ) -> Result<Option<&str>, ConfigValueParseError> {
+ ) -> Result<Option<&str>, HgError> {
self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
str::from_utf8(value).ok()
})
@@ -388,7 +426,7 @@
&self,
section: &[u8],
item: &[u8],
- ) -> Result<Option<u32>, ConfigValueParseError> {
+ ) -> Result<Option<u32>, HgError> {
self.get_parse(section, item, "valid integer", |value| {
str::from_utf8(value).ok()?.parse().ok()
})
@@ -401,7 +439,7 @@
&self,
section: &[u8],
item: &[u8],
- ) -> Result<Option<u64>, ConfigValueParseError> {
+ ) -> Result<Option<u64>, HgError> {
self.get_parse(section, item, "byte quantity", values::parse_byte_size)
}
@@ -412,7 +450,7 @@
&self,
section: &[u8],
item: &[u8],
- ) -> Result<Option<bool>, ConfigValueParseError> {
+ ) -> Result<Option<bool>, HgError> {
self.get_parse(section, item, "boolean", values::parse_bool)
}
@@ -422,7 +460,7 @@
&self,
section: &[u8],
item: &[u8],
- ) -> Result<bool, ConfigValueParseError> {
+ ) -> Result<bool, HgError> {
Ok(self.get_option(section, item)?.unwrap_or(false))
}