1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
#![doc = include_str!("../README.md")]
use linked_hash_map::Entry;
use yaml_rust::yaml::Hash;
pub use yaml_rust::Yaml;
use yaml_rust::{YamlEmitter, YamlLoader};
#[cfg(test)]
mod tests;
/**
YAML Hash with merge/update capabilities
Wrapper around `yaml_rust::yaml::Hash`, which is a type alias for `linked_hash_map::LinkedHashMap`
*/
#[derive(Debug, Default)]
pub struct MergeYamlHash {
pub data: Hash,
}
impl MergeYamlHash {
/**
Create a new/empty hash
```
use merge_yaml_hash::MergeYamlHash;
let mut hash = MergeYamlHash::new();
assert_eq!(format!("{:?}", hash), "MergeYamlHash { data: {} }");
assert_eq!(format!("{:#?}", hash), "MergeYamlHash {\n data: {},\n}");
```
*/
pub fn new() -> MergeYamlHash {
MergeYamlHash::default()
}
/**
Merge YAML file or string
```
use merge_yaml_hash::MergeYamlHash;
let mut hash = MergeYamlHash::new();
hash.merge("tests/a.yaml");
assert_eq!(hash.to_string(), "apple: 1\nbanana: 2");
```
```
use merge_yaml_hash::MergeYamlHash;
let mut hash = MergeYamlHash::new();
let yaml = "apple: 1\nbanana: 2".to_string();
hash.merge(&yaml);
assert_eq!(hash.to_string(), yaml);
```
*/
pub fn merge(&mut self, file_or_str: &str) {
let path = std::path::Path::new(&file_or_str);
let yaml = if path.is_file() {
std::fs::read_to_string(path).unwrap()
} else {
file_or_str.to_string()
};
for doc in YamlLoader::load_from_str(&yaml).unwrap() {
if let Yaml::Hash(h) = doc {
self.data = merge_hashes(&self.data, &h);
}
}
}
/**
Merge multiple YAML files or strings in order
No conflicts:
```
use merge_yaml_hash::MergeYamlHash;
let mut hash = MergeYamlHash::new();
let yaml1 = "apple: 1\nbanana: 2".to_string();
let yaml2 = "cherry: 3".to_string();
let result = "apple: 1\nbanana: 2\ncherry: 3";
hash.merge_vec(vec![yaml1, yaml2]);
assert_eq!(hash.to_string(), result);
```
With conflict; value in `yaml2.banana` simply overrides the value in
`yaml1.banana`:
```
use merge_yaml_hash::MergeYamlHash;
let mut hash = MergeYamlHash::new();
let yaml1 = "apple: 1\nbanana: 2".to_string();
let yaml2 = "banana: 3".to_string();
let result = "apple: 1\nbanana: 3";
hash.merge_vec(vec![yaml1, yaml2]);
assert_eq!(hash.to_string(), result);
```
*/
pub fn merge_vec(&mut self, files_or_strings: Vec<String>) {
for file_or_string in files_or_strings {
self.merge(&file_or_string);
}
}
}
impl std::fmt::Display for MergeYamlHash {
/**
Serialize to YAML string
```
use merge_yaml_hash::MergeYamlHash;
let mut hash = MergeYamlHash::new();
let yaml = "apple: 1\nbanana: 2".to_string();
hash.merge(&yaml);
assert_eq!(hash.to_string(), yaml);
```
*/
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut r = String::new();
let mut emitter = YamlEmitter::new(&mut r);
let yaml = Yaml::Hash(self.data.clone());
emitter.dump(&yaml).unwrap();
r.replace_range(..4, ""); // remove "---\n" at beginning
write!(f, "{r}")
}
}
/**
Merge two YAML hashes
*/
fn merge_hashes(a: &Hash, b: &Hash) -> Hash {
let mut r = a.clone();
for (k, v) in b.iter() {
if let Yaml::Hash(bh) = v {
if let Entry::Occupied(e) = r.entry(k.clone()) {
if let Yaml::Hash(rh) = e.get().clone() {
r.entry(k.clone())
.and_modify(|e| *e = Yaml::Hash(merge_hashes(&rh, bh)))
.or_insert_with(|| Yaml::Hash(merge_hashes(&rh, bh)));
continue;
}
}
}
r.entry(k.clone())
.and_modify(|e| *e = v.clone())
.or_insert_with(|| v.clone());
}
r
}