Tables in config.extra can be merged with those in theme.extra (#1100)

* Tables in config.extra can be merged with those in theme.extra

* Don't panic with invalid config type, but propagate an error

* Recursively merge config/theme extra sections

Co-authored-by: southerntofu <southerntofu@thunix.net>
This commit is contained in:
southerntofu 2020-07-29 07:37:49 +00:00 committed by GitHub
parent 66aaf4e384
commit 7e7bf2bcd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -206,19 +206,14 @@ impl Config {
/// Merges the extra data from the theme with the config extra data /// Merges the extra data from the theme with the config extra data
fn add_theme_extra(&mut self, theme: &Theme) -> Result<()> { fn add_theme_extra(&mut self, theme: &Theme) -> Result<()> {
// 3 pass merging
// 1. save config to preserve user
let original = self.extra.clone();
// 2. inject theme extra values
for (key, val) in &theme.extra { for (key, val) in &theme.extra {
self.extra.entry(key.to_string()).or_insert_with(|| val.clone()); if !self.extra.contains_key(key) {
// The key is not overriden in site config, insert it
self.extra.insert(key.to_string(), val.clone());
continue;
}
merge(self.extra.get_mut(key).unwrap(), val)?;
} }
// 3. overwrite with original config
for (key, val) in &original {
self.extra.entry(key.to_string()).or_insert_with(|| val.clone());
}
Ok(()) Ok(())
} }
@ -280,6 +275,34 @@ impl Config {
} }
} }
// merge TOML data that can be a table, or anything else
pub fn merge(into: &mut Toml, from: &Toml) -> Result<()> {
match (from.is_table(), into.is_table()) {
(false, false) => {
// These are not tables so we have nothing to merge
Ok(())
},
(true, true) => {
// Recursively merge these tables
let into_table = into.as_table_mut().unwrap();
for (key, val) in from.as_table().unwrap() {
if !into_table.contains_key(key) {
// An entry was missing in the first table, insert it
into_table.insert(key.to_string(), val.clone());
continue;
}
// Two entries to compare, recurse
merge(into_table.get_mut(key).unwrap(), val)?;
}
Ok(())
},
_ => {
// Trying to merge a table with something else
Err(Error::msg(&format!("Cannot merge config.toml with theme.toml because the following values have incompatibles types:\n- {}\n - {}", into, from)))
}
}
}
impl Default for Config { impl Default for Config {
fn default() -> Config { fn default() -> Config {
Config { Config {
@ -416,18 +439,32 @@ base_url = "https://replace-this-with-your-url.com"
[extra] [extra]
hello = "world" hello = "world"
[extra.sub]
foo = "bar"
[extra.sub.sub]
foo = "bar"
"#; "#;
let mut config = Config::parse(config_str).unwrap(); let mut config = Config::parse(config_str).unwrap();
let theme_str = r#" let theme_str = r#"
[extra] [extra]
hello = "foo" hello = "foo"
a_value = 10 a_value = 10
[extra.sub]
foo = "default"
truc = "default"
[extra.sub.sub]
foo = "default"
truc = "default"
"#; "#;
let theme = Theme::parse(theme_str).unwrap(); let theme = Theme::parse(theme_str).unwrap();
assert!(config.add_theme_extra(&theme).is_ok()); assert!(config.add_theme_extra(&theme).is_ok());
let extra = config.extra; let extra = config.extra;
assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string()); assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string());
assert_eq!(extra["a_value"].as_integer().unwrap(), 10); assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
assert_eq!(extra["sub"]["foo"].as_str().unwrap(), "bar".to_string());
assert_eq!(extra["sub"].get("truc").expect("The whole extra.sub table was overriden by theme data, discarding extra.sub.truc").as_str().unwrap(), "default".to_string());
assert_eq!(extra["sub"]["sub"]["foo"].as_str().unwrap(), "bar".to_string());
assert_eq!(extra["sub"]["sub"].get("truc").expect("Failed to merge subsubtable extra.sub.sub").as_str().unwrap(), "default".to_string());
} }
const CONFIG_TRANSLATION: &str = r#" const CONFIG_TRANSLATION: &str = r#"
@ -585,4 +622,26 @@ languages = [
let err = config.unwrap_err(); let err = config.unwrap_err();
assert_eq!("Default language `fr` should not appear both in `config.default_language` and `config.languages`", format!("{}", err)); assert_eq!("Default language `fr` should not appear both in `config.default_language` and `config.languages`", format!("{}", err));
} }
#[test]
fn cannot_overwrite_theme_mapping_with_invalid_type() {
let config_str = r#"
base_url = "http://localhost:1312"
default_language = "fr"
[extra]
foo = "bar"
"#;
let mut config = Config::parse(config_str).unwrap();
let theme_str = r#"
[extra]
[extra.foo]
bar = "baz"
"#;
let theme = Theme::parse(theme_str).unwrap();
// We expect an error here
assert_eq!(false, config.add_theme_extra(&theme).is_ok());
}
} }