Skip to content

Commit

Permalink
config: add method to iterate over merged table keys
Browse files Browse the repository at this point in the history
.get_table() callers will be migrated to .table_keys() + .get() to attach source
indication to error message. It might be a bit more expensive, but we can get by
without introducing table wrapper that tracks source config layers.
  • Loading branch information
yuja committed Dec 9, 2024
1 parent 6b67d5e commit 514803d
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 1 deletion.
43 changes: 42 additions & 1 deletion lib/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,9 @@ impl StackedConfig {
}

/// Looks up sub table from all layers, merges fields as needed.
// TODO: redesign this to attach better error indication?
///
/// Use `table_keys(prefix)` and `get([prefix, key])` instead if table
/// values have to be converted to non-generic value type.
pub fn get_table(&self, name: impl ToConfigNamePath) -> Result<ConfigTable, ConfigGetError> {
self.get(name)
}
Expand All @@ -501,6 +503,20 @@ impl StackedConfig {
source_path: self.layers[layer_index].path.clone(),
})
}

/// Returns iterator over sub table keys in order of layer precedence.
/// Duplicated keys are omitted.
pub fn table_keys(&self, name: impl ToConfigNamePath) -> impl Iterator<Item = &str> {
let name = name.into_name_path();
let name = name.borrow();
let to_merge = get_tables_to_merge(&self.layers, name);
to_merge
.into_iter()
.rev()
// TODO: remove sorting after migrating to toml_edit
.flat_map(|table| table.keys().sorted_unstable().map(|k| k.as_ref()))
.unique()
}
}

/// Looks up item from `layers`, merges sub fields as needed. Returns a merged
Expand Down Expand Up @@ -537,6 +553,22 @@ fn get_merged_item(
Some((merged, top_index))
}

/// Looks up tables to be merged from `layers`, returns in reverse order.
fn get_tables_to_merge<'a>(
layers: &'a [ConfigLayer],
name: &ConfigNamePathBuf,
) -> Vec<&'a ConfigTable> {
let mut to_merge = Vec::new();
for layer in layers.iter().rev() {
match layer.look_up_table(name) {
Ok(Some(table)) => to_merge.push(table),
Ok(None) => {} // parent is a table, but no value found
Err(_) => break, // parent/leaf is not a table, shadows lower layers
}
}
to_merge
}

/// Merges `upper_item` fields into `lower_item` recursively.
fn merge_items(lower_item: &mut ConfigValue, upper_item: &ConfigValue) {
// TODO: If we switch to toml_edit, inline table will probably be treated as
Expand Down Expand Up @@ -751,6 +783,10 @@ mod tests {
c = 'a.c #1'
"});
assert_eq!(config.get_table("a").unwrap(), expected);
assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
}

#[test]
Expand All @@ -772,6 +808,8 @@ mod tests {
a.b = 'a.a.b #2'
"});
assert_eq!(config.get_table("a").unwrap(), expected);
assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
}

#[test]
Expand All @@ -794,6 +832,8 @@ mod tests {
b = 'a.b #0'
"});
assert_eq!(config.get_table("a").unwrap(), expected);
assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
}

#[test]
Expand All @@ -815,5 +855,6 @@ mod tests {
"});
// a is not under a.a, but it should still shadow lower layers
assert_eq!(config.get_table("a.a").unwrap(), expected);
assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
}
}
8 changes: 8 additions & 0 deletions lib/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,17 @@ impl UserSettings {
}

/// Looks up sub table by `name`.
///
/// Use `table_keys(prefix)` and `get([prefix, key])` instead if table
/// values have to be converted to non-generic value type.
pub fn get_table(&self, name: impl ToConfigNamePath) -> Result<ConfigTable, ConfigGetError> {
self.config.get_table(name)
}

/// Returns iterator over sub table keys at `name`.
pub fn table_keys(&self, name: impl ToConfigNamePath) -> impl Iterator<Item = &str> {
self.config.table_keys(name)
}
}

/// This Rng uses interior mutability to allow generating random values using an
Expand Down

0 comments on commit 514803d

Please sign in to comment.