1use self::mutations::transforms::Transformer;
4use self::mutations::Action;
5use self::mutations::Mutations;
6use self::mutations::SectionAction;
7use crate::loader::Loader;
8use crate::loader::{self};
9use crate::source_loader::SectionAndKey;
10use crate::source_loader::SourceIni;
11use crate::source_loader::SourceValue;
12use crate::source_loader::{self};
13use lending_iterator::prelude::*;
14use log::error;
15use std::borrow::Cow;
16use std::collections::HashSet;
17use std::io::Read;
18use thiserror::Error;
19
20pub mod mutations;
21
22#[cfg(test)]
23mod tests;
24
25#[derive(Debug, Error)]
27#[non_exhaustive]
28pub enum MergeError {
29 #[error("Failed to load target INI due to {0}")]
31 TargetLoad(#[source] Box<dyn std::error::Error + 'static + Send + Sync>),
32 #[error("Failed to load source INI due to {0}")]
34 SourceLoad(#[source] Box<dyn std::error::Error + 'static + Send + Sync>),
35}
36
37#[derive(Debug)]
39struct MergeState {
40 result: Vec<String>,
42 pending_lines: Vec<String>,
45 seen_sections: HashSet<String>,
47 seen_keys: HashSet<String>,
50 cur_section: String,
52}
53
54impl MergeState {
55 fn new() -> Self {
56 Self {
57 result: Vec::default(),
58 pending_lines: Vec::default(),
59 seen_sections: HashSet::default(),
60 seen_keys: HashSet::default(),
61 cur_section: crate::OUTSIDE_SECTION.to_string(),
62 }
63 }
64
65 fn push_raw(&mut self, raw: String) {
67 if self.pending_lines.is_empty() {
68 self.result.push(raw);
69 } else {
70 self.pending_lines.push(raw);
71 }
72 }
73
74 fn emit_pending_lines(&mut self) {
82 self.result.append(&mut self.pending_lines);
83 }
84
85 fn emit_non_target_lines(&mut self, source: &SourceIni, mutations: &Mutations) {
89 if source.has_section(self.cur_section.as_str()) {
90 match mutations.find_section_action(self.cur_section.as_str()) {
91 None => {
92 let mut unseen_entries: Vec<_> = source
93 .section_entries(&self.cur_section)
94 .filter(|e| !self.seen_keys.contains(e.0.as_ref()))
95 .collect();
96 unseen_entries.sort_by_key(|e| e.0);
97 for (key, value) in unseen_entries {
98 let action = mutations.find_action(self.cur_section.as_str(), key);
99 self.seen_keys.insert(key.to_string());
100 self.emit_kv(action.as_deref(), key, Some(value), None);
101 }
102 }
103 Some(SectionAction::Ignore) => (),
104 Some(SectionAction::Delete) => (),
105 }
106 }
107 self.emit_force_keys(mutations);
108
109 self.seen_keys.clear();
110 }
111
112 fn emit_force_keys(&mut self, mutations: &Mutations) {
114 if let Some(forced_keys) = mutations.forced_keys.get(&self.cur_section) {
115 self.emit_pending_lines();
116 let mut forced_keys: Vec<_> = forced_keys
117 .iter()
118 .filter(|&e| !self.seen_keys.contains(e))
119 .collect();
120 forced_keys.sort();
121 for key in forced_keys {
122 let action = mutations.find_action(self.cur_section.as_str(), key);
123 self.emit_kv(action.as_deref(), key, None, None);
124 }
125 }
126 }
127
128 fn emit_kv(
131 &mut self,
132 action: Option<&Action>,
133 key: &str,
134 source: Option<&SourceValue>,
135 target: Option<ini_roundtrip::Item<'_>>,
136 ) {
137 match action {
138 None => {
139 match source {
140 Some(val) => self.result.push(val.raw().into()),
141 None => panic!("This should never happen"),
145 }
146 }
147 Some(Action::Ignore) => (),
148 Some(Action::Delete) => (),
149 Some(Action::Transform(transform)) => {
150 let src =
151 source.map(|v| crate::Property::from_src(self.cur_section.as_str(), key, v));
152 let tgt = target
153 .and_then(|v| crate::Property::try_from_ini(self.cur_section.as_str(), v));
154 let transform_result = transform.call(&src, &tgt);
155 match transform_result {
156 Ok(mutations::transforms::TransformerAction::Nothing) => (),
157 Ok(mutations::transforms::TransformerAction::Line(raw_line)) => {
158 self.result.push(raw_line.into_owned());
159 }
160 Err(e) => {
161 error!(target: "ini-merge", "Failed to transform key {key}: {e}");
162 }
163 }
164 }
165 }
166 }
167}
168
169pub(crate) fn merge<'a>(
171 target: &'a mut Loader,
172 source: &'a SourceIni,
173 mutations: &Mutations,
174) -> Vec<String> {
175 let mut state = MergeState::new();
176
177 while let Some(ref entry) = target.next() {
178 match *entry {
179 ini_roundtrip::Item::Error(raw) => {
180 error!(target: "ini-merge", "Failed to parse line, copying verbatim: {raw}");
181 state.push_raw(raw.into());
182 }
183 ini_roundtrip::Item::Comment { raw } | ini_roundtrip::Item::Blank { raw } => {
184 state.push_raw(raw.into());
185 }
186 ini_roundtrip::Item::Section { name, raw } => {
187 state.emit_non_target_lines(source, mutations);
190 state.cur_section.clear();
192 state.cur_section.push_str(name);
193 state.seen_sections.insert(name.into());
194 state.seen_keys.clear();
195 state.pending_lines.clear();
196
197 match mutations.find_section_action(name) {
198 Some(SectionAction::Ignore) => state.push_raw(raw.into()),
199 None if source.has_section(name) => state.push_raw(raw.into()),
200 None => state.pending_lines.push(raw.into()),
204 Some(SectionAction::Delete) => (),
206 }
207 }
208 ini_roundtrip::Item::SectionEnd => (),
209 target @ ini_roundtrip::Item::Property { key, val: _, raw } => {
210 let action = mutations.find_action(&state.cur_section, key);
212 let src_property = source.property(&SectionAndKey::new(
213 Cow::Owned(state.cur_section.clone()),
214 Cow::Borrowed(key),
215 ));
216 match action.as_deref() {
217 None => {
218 if let Some(src_val) = src_property {
219 state.seen_keys.insert(key.into());
220 state.emit_pending_lines();
221 state.emit_kv(action.as_deref(), key, Some(src_val), Some(target));
222 }
223 }
224 Some(Action::Ignore) => {
225 state.seen_keys.insert(key.into());
226 state.emit_pending_lines();
227 state.result.push(raw.into());
228 }
229 Some(Action::Delete) => {
230 }
232 Some(Action::Transform(_)) => {
233 state.seen_keys.insert(key.into());
234 state.emit_pending_lines();
235 state.emit_kv(action.as_deref(), key, src_property, Some(target));
236 }
237 }
238 }
239 }
240 }
241
242 state.emit_non_target_lines(source, mutations);
244
245 let mut unseen_sections: HashSet<_> = source
247 .sections()
248 .filter(|x| !state.seen_sections.contains(x.0))
249 .map(|(section, raw)| (section, raw.to_owned()))
250 .collect();
251 unseen_sections.extend(
254 mutations
255 .forced_keys
256 .keys()
257 .filter(|&x| !state.seen_sections.contains(x))
258 .map(|section| (section, format!("[{section}]"))),
259 );
260 let mut unseen_sections: Vec<_> = unseen_sections.into_iter().collect();
261 unseen_sections.sort_by_key(|e| e.0);
262 for (section, raw) in unseen_sections {
263 if section == crate::OUTSIDE_SECTION {
264 continue;
266 }
267 match mutations.find_section_action(section) {
268 None => (),
269 Some(SectionAction::Ignore) => continue,
270 Some(SectionAction::Delete) => continue,
271 }
272 state.cur_section.clear();
273 state.cur_section.push_str(section);
274 state.seen_keys.clear();
275 state.seen_sections.insert(section.into());
276 state.pending_lines.clear();
277
278 state.result.push(raw.clone());
279 for (key, value) in source.section_entries(section) {
280 let action = mutations.find_action(section, key);
281 state.seen_keys.insert(key.to_string());
282 state.emit_kv(action.as_deref(), key, Some(value), None);
283 }
284 state.emit_force_keys(mutations);
285 }
286
287 state.result
288}
289
290pub fn merge_ini(
293 target: &mut impl Read,
294 source: &mut impl Read,
295 mutations: &Mutations,
296) -> Result<Vec<String>, MergeError> {
297 let mut target =
298 loader::load_ini(target).map_err(|inner| MergeError::TargetLoad(inner.into()))?;
299 let source = source_loader::load_source_ini(source)
300 .map_err(|inner| MergeError::SourceLoad(inner.into()))?;
301 Ok(merge(&mut target, &source, mutations))
302}