Skip to main content

code_ranker_plugin_api/
toml_merge.rs

1//! Generic TOML table **inheritance merge** — the primitive both the language
2//! plugins (`defaults.toml ⊕ [base] ⊕ <lang>.toml`) and the CLI (built-in defaults
3//! ⊕ a project `code-ranker.toml`) layer config with. Lives in
4//! `code-ranker-plugin-api` so neither consumer reaches into a sibling crate.
5//!
6//! ## Merge semantics ([`deep_merge`])
7//!
8//! For each key of `overlay` applied onto `base`:
9//! - **table vs table** → recurse (per-key deep merge).
10//! - **`[[presets]]` array of tables** → merge **by `id`**: an overlay preset with
11//!   an `id` already present in the base replaces that entry in place; a new `id`
12//!   is appended.
13//! - **array patched by an op-table** (`{add,remove,replace,clear,prepend,…}`) →
14//!   the inherited list is **mutated in place** (see [`crate::list_override`]); a
15//!   plain array still replaces it wholesale.
16//! - **any other value** (scalar, plain array, table-vs-non-table) → the overlay
17//!   value **replaces** the base value outright.
18//!
19//! Keys present only in one side are kept as-is.
20
21use crate::list_override::{is_list_op_table, patch_value_list};
22use toml::{Table, Value};
23
24/// Deep-merge `overlay` onto `base` (see module docs for the rules).
25pub fn deep_merge(mut base: Table, overlay: Table) -> Table {
26    for (key, ov) in overlay {
27        match base.remove(&key) {
28            Some(Value::Table(bt)) => match ov {
29                Value::Table(ot) => {
30                    base.insert(key, Value::Table(deep_merge(bt, ot)));
31                }
32                other => {
33                    base.insert(key, other);
34                }
35            },
36            Some(Value::Array(ba)) if key == "presets" => {
37                if let Value::Array(oa) = ov {
38                    base.insert(key, Value::Array(merge_presets(ba, oa)));
39                } else {
40                    base.insert(key, ov);
41                }
42            }
43            // An inherited list patched by an op-table (`{add,remove,replace,
44            // clear,prepend}`) is mutated in place; a plain array replaces it
45            // wholesale (the historical behaviour). See `crate::list_override`.
46            Some(Value::Array(ba)) => match &ov {
47                Value::Table(t) if is_list_op_table(t) => {
48                    let patched = patch_value_list(ba, &ov);
49                    base.insert(key, Value::Array(patched));
50                }
51                _ => {
52                    base.insert(key, ov);
53                }
54            },
55            _ => {
56                base.insert(key, ov);
57            }
58        }
59    }
60    base
61}
62
63/// Merge two `[[presets]]` arrays by the `id` field: an overlay preset whose
64/// `id` matches a base entry replaces it in place; a new `id` is appended.
65/// Entries without a string `id` are appended verbatim.
66pub fn merge_presets(mut base: Vec<Value>, overlay: Vec<Value>) -> Vec<Value> {
67    for ov in overlay {
68        let ov_id = preset_id(&ov);
69        match ov_id.and_then(|id| base.iter().position(|b| preset_id(b) == Some(id))) {
70            Some(pos) => base[pos] = ov,
71            None => base.push(ov),
72        }
73    }
74    base
75}
76
77fn preset_id(v: &Value) -> Option<&str> {
78    v.as_table()?.get("id")?.as_str()
79}