Skip to main content

ephemeris_documents/
merge.rs

1//! The format independent merge of a desired tree into a current one.
2
3use std::collections::HashSet;
4
5use crate::error::MergeError;
6use crate::ownership::{Claim, Ownership};
7use crate::path::KeyPath;
8
9/// A node of a format's document tree that the merge can edit in place.
10pub(crate) trait Node: Clone {
11    /// Take the child `key` from `source`, replacing the existing child or
12    /// appending. An existing child keeps its decor unless the desired child
13    /// brings a comment of its own, which replaces the leading comment.
14    fn adopt(&mut self, key: &str, source: &Self);
15    /// Insert an empty container for the child `key` of `source`, with the
16    /// decor and comment of that child.
17    fn adopt_shell(&mut self, key: &str, source: &Self);
18    fn child(&self, key: &str) -> Option<&Self>;
19    fn child_mut(&mut self, key: &str) -> Option<&mut Self>;
20    /// Whether this node holds keyed children.
21    fn is_container(&self) -> bool;
22    /// Whether this node has no children.
23    fn is_empty(&self) -> bool {
24        self.keys().next().is_none()
25    }
26    /// The child keys in order. Empty for leaves.
27    fn keys(&self) -> impl Iterator<Item = &str> + '_;
28    fn remove(&mut self, key: &str);
29}
30
31struct Merge<'a> {
32    ownership: &'a Ownership,
33}
34
35impl Merge<'_> {
36    fn container<N: Node>(
37        &self,
38        current: &mut N,
39        desired: Option<&N>,
40        path: &mut Vec<String>,
41    ) -> Result<(), MergeError> {
42        if !current.is_container() || desired.is_some_and(|node| !node.is_container()) {
43            return Err(MergeError::TypeConflict {
44                path: KeyPath::from(path.as_slice()),
45            });
46        }
47        let mut keys: Vec<String> = current.keys().map(str::to_owned).collect();
48        let added: Vec<String> = {
49            let known: HashSet<&str> = keys.iter().map(String::as_str).collect();
50            desired
51                .into_iter()
52                .flat_map(Node::keys)
53                .filter(|key| !known.contains(key))
54                .map(str::to_owned)
55                .collect()
56        };
57        keys.extend(added);
58        for key in &keys {
59            path.push(key.clone());
60            let result = self.entry(current, desired, key, path);
61            path.pop();
62            result?;
63        }
64        Ok(())
65    }
66
67    /// Merge the child `key`, whose path is the end of `path`.
68    fn entry<N: Node>(
69        &self,
70        current: &mut N,
71        desired: Option<&N>,
72        key: &str,
73        path: &mut Vec<String>,
74    ) -> Result<(), MergeError> {
75        match self.ownership.claim(path) {
76            Claim::Owned => {
77                match desired.filter(|node| node.child(key).is_some()) {
78                    Some(parent) => current.adopt(key, parent),
79                    None => current.remove(key),
80                }
81                Ok(())
82            }
83            Claim::Unmanaged => Ok(()),
84            Claim::Mixed => self.mixed(current, desired, key, path),
85        }
86    }
87
88    fn mixed<N: Node>(
89        &self,
90        current: &mut N,
91        desired: Option<&N>,
92        key: &str,
93        path: &mut Vec<String>,
94    ) -> Result<(), MergeError> {
95        let current_child = current.child(key);
96        let current_is_container = current_child.map(Node::is_container);
97        match (
98            current_is_container,
99            desired.and_then(|node| node.child(key)),
100        ) {
101            (None, None) => Ok(()),
102            (None, Some(node)) => {
103                if !node.is_container() {
104                    return Err(MergeError::TypeConflict {
105                        path: KeyPath::from(path.as_slice()),
106                    });
107                }
108                if let Some(parent) = desired {
109                    current.adopt_shell(key, parent);
110                }
111                self.descend(current, Some(node), key, path)?;
112                let hollow = current.child(key).is_some_and(Node::is_empty);
113                if hollow && !node.is_empty() {
114                    current.remove(key);
115                }
116                Ok(())
117            }
118            (Some(true), None) => self.descend(current, None, key, path),
119            (Some(false), None) => {
120                if self.ownership.default_claim().is_owned() {
121                    current.remove(key);
122                }
123                Ok(())
124            }
125            (Some(_), Some(node)) => self.descend(current, Some(node), key, path),
126        }
127    }
128
129    fn descend<N: Node>(
130        &self,
131        current: &mut N,
132        desired: Option<&N>,
133        key: &str,
134        path: &mut Vec<String>,
135    ) -> Result<(), MergeError> {
136        current
137            .child_mut(key)
138            .map_or(Ok(()), |child| self.container(child, desired, path))
139    }
140}
141
142/// Merge `desired` into `current`, touching only what `ownership` claims.
143///
144/// # Errors
145/// Returns an error when a path that holds both owned and unmanaged values is
146/// not a container on both sides.
147pub(crate) fn merge<N: Node>(
148    current: &mut N,
149    desired: &N,
150    ownership: &Ownership,
151) -> Result<(), MergeError> {
152    match ownership.claim(&[]) {
153        Claim::Owned => {
154            current.clone_from(desired);
155            Ok(())
156        }
157        Claim::Unmanaged => Ok(()),
158        Claim::Mixed => Merge { ownership }.container(current, Some(desired), &mut Vec::new()),
159    }
160}
161
162#[cfg(test)]
163mod tests;