Skip to main content

cfgd_core/util/
yaml_merge.rs

1use crate::config;
2
3/// Deep merge two YAML values. Mappings are merged recursively; all other
4/// types are replaced by the overlay value.
5pub fn deep_merge_yaml(base: &mut serde_yaml::Value, overlay: &serde_yaml::Value) {
6    match (base, overlay) {
7        (serde_yaml::Value::Mapping(base_map), serde_yaml::Value::Mapping(overlay_map)) => {
8            for (key, value) in overlay_map {
9                if let Some(base_value) = base_map.get_mut(key) {
10                    deep_merge_yaml(base_value, value);
11                } else {
12                    base_map.insert(key.clone(), value.clone());
13                }
14            }
15        }
16        (base, overlay) => {
17            *base = overlay.clone();
18        }
19    }
20}
21
22/// Extend a `Vec<String>` with items from `source`, skipping duplicates.
23pub fn union_extend(target: &mut Vec<String>, source: &[String]) {
24    let mut existing: std::collections::HashSet<String> = target.iter().cloned().collect();
25    for item in source {
26        if existing.insert(item.clone()) {
27            target.push(item.clone());
28        }
29    }
30}
31
32/// Merge env vars by name: later entries override earlier ones with the same name.
33/// Used by config layer merging, composition, and reconciler module merge.
34pub fn merge_env(base: &mut Vec<config::EnvVar>, updates: &[config::EnvVar]) {
35    let mut index: std::collections::HashMap<String, usize> = base
36        .iter()
37        .enumerate()
38        .map(|(i, e)| (e.name.clone(), i))
39        .collect();
40    for ev in updates {
41        if let Some(&pos) = index.get(&ev.name) {
42            base[pos] = ev.clone();
43        } else {
44            index.insert(ev.name.clone(), base.len());
45            base.push(ev.clone());
46        }
47    }
48}
49
50/// Merge shell aliases by name: later entries override earlier ones with the same name.
51/// Same semantics as `merge_env`.
52pub fn merge_aliases(base: &mut Vec<config::ShellAlias>, updates: &[config::ShellAlias]) {
53    let mut index: std::collections::HashMap<String, usize> = base
54        .iter()
55        .enumerate()
56        .map(|(i, a)| (a.name.clone(), i))
57        .collect();
58    for alias in updates {
59        if let Some(&pos) = index.get(&alias.name) {
60            base[pos] = alias.clone();
61        } else {
62            index.insert(alias.name.clone(), base.len());
63            base.push(alias.clone());
64        }
65    }
66}
67
68/// Split a list of values into adds and removes.
69///
70/// Values starting with `-` are treated as removals (the leading `-` is stripped).
71/// All other values are adds. This powers the unified `--thing` CLI flags where
72/// `--thing foo` adds and `--thing -foo` removes.
73pub fn split_add_remove(values: &[String]) -> (Vec<String>, Vec<String>) {
74    let mut adds = Vec::new();
75    let mut removes = Vec::new();
76    for v in values {
77        if let Some(stripped) = v.strip_prefix('-') {
78            removes.push(stripped.to_string());
79        } else {
80            adds.push(v.clone());
81        }
82    }
83    (adds, removes)
84}