module 0.3.1

Modular NixOS-style configuration crate.
Documentation
use core::cmp::Ord;
use core::fmt::Display;

use alloc::boxed::Box;
use alloc::collections::{BTreeMap, BTreeSet, LinkedList};
use alloc::vec::Vec;

use super::prelude::*;

unmergeable! {
    Box<core::ffi::CStr>, Box<str>,
    alloc::ffi::CString,
    alloc::string::String
}

impl<T> Merge for Box<[T]> {
    unmergeable!();
}

impl<T> Merge for alloc::borrow::Cow<'_, T>
where
    T: ?Sized + alloc::borrow::ToOwned,
    T::Owned: Merge,
{
    fn merge_ref(&mut self, other: Self) -> Result<(), Error> {
        match self {
            Self::Owned(x) => x.merge_ref(other.into_owned()),
            Self::Borrowed(x) => {
                let mut x = x.to_owned();
                x.merge_ref(other.into_owned())?;
                *self = Self::Owned(x);
                Ok(())
            }
        }
    }
}

impl<T> Merge for Box<T>
where
    T: Merge,
{
    fn merge_ref(&mut self, other: Self) -> Result<(), Error> {
        T::merge_ref(self, *other)
    }
}

impl<T> Merge for Vec<T> {
    fn merge_ref(&mut self, mut other: Self) -> Result<(), Error> {
        self.append(&mut other);
        Ok(())
    }
}

impl<T> Merge for LinkedList<T> {
    fn merge_ref(&mut self, mut other: Self) -> Result<(), Error> {
        self.append(&mut other);
        Ok(())
    }
}

impl<K, V> Merge for BTreeMap<K, V>
where
    K: Ord + Display,
    V: Merge,
{
    fn merge_ref(&mut self, other: Self) -> Result<(), Error> {
        use alloc::collections::btree_map::Entry;

        for (k, b) in other {
            match self.entry(k) {
                Entry::Vacant(x) => {
                    x.insert(b);
                }
                Entry::Occupied(x) => {
                    let (k, a) = x.remove_entry();
                    let merged = a.merge(b).with_value(|| format!("\"{k}\""))?;
                    self.insert(k, merged);
                }
            }
        }

        Ok(())
    }
}

impl<T> Merge for BTreeSet<T>
where
    T: Ord,
{
    fn merge_ref(&mut self, mut other: Self) -> Result<(), Error> {
        self.append(&mut other);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test::*;

    #[test]
    fn test_box() {
        let a = Box::new(Merged(false));
        let b = Box::new(Merged(false));

        let c = a.merge(b).unwrap();
        assert!((*c).0);
    }

    #[test]
    fn test_vec() {
        use alloc::vec;

        let a = vec![1, 2, 5, 7, 0];
        let b = vec![2, 8, 9, 10];

        let c = a.merge(b).unwrap();
        assert_eq!(c, &[1, 2, 5, 7, 0, 2, 8, 9, 10]);
    }

    #[test]
    fn test_linked_list() {
        let a: LinkedList<i32> = [1, 2, 5, 7, 0].into_iter().collect();
        let b: LinkedList<i32> = [2, 8, 9, 10].into_iter().collect();

        let c = a.merge(b).unwrap();
        assert!(c.iter().eq(&[1, 2, 5, 7, 0, 2, 8, 9, 10]));
    }

    #[test]
    fn test_btree_map() {
        fn from_keys(keys: &[&'static str]) -> BTreeMap<&'static str, Merged> {
            keys.iter()
                .copied()
                .map(|k| (k, Merged::default()))
                .collect()
        }

        let a = from_keys(&["key1", "key2", "key3", "key4", "key7"]);
        let b = from_keys(&["key5", "key1", "key7", "key2", "key6"]);

        let c = a.merge(b).unwrap();

        let expected = [
            ("key1", Merged(true)),
            ("key2", Merged(true)),
            ("key3", Merged(false)),
            ("key4", Merged(false)),
            ("key5", Merged(false)),
            ("key6", Merged(false)),
            ("key7", Merged(true)),
        ];

        assert_eq!(expected.len(), c.len());

        for (k, v) in expected {
            assert_eq!(c[k].0, v.0, "key: {k}");
        }
    }

    #[test]
    fn test_btree_set() {
        let a: BTreeSet<i32> = [1, 2, 5, 7, 0, 10].into_iter().collect();
        let b: BTreeSet<i32> = [2, 8, 9, 10, 5].into_iter().collect();

        let mut c: Vec<i32> = a.merge(b).unwrap().into_iter().collect();
        c.sort_unstable();
        assert_eq!(c, &[0, 1, 2, 5, 7, 8, 9, 10]);
    }
}