cargo-sync-rdme 0.8.0

Cargo subcommand to synchronize README with crate documentation
use std::{collections::HashMap, hash::Hash};

use indexmap::IndexMap;

pub(in crate::config) trait ApplyLayer {
    fn apply_layer(&mut self, layer: &Self);
}

impl<T> ApplyLayer for Option<T>
where
    T: ApplyLayer + Clone,
{
    fn apply_layer(&mut self, layer: &Self) {
        match (self, layer) {
            (Some(target), Some(layer)) => target.apply_layer(layer),
            (Some(_target), None) => {}
            (target @ None, layer) => target.clone_from(layer),
        }
    }
}

impl<T> ApplyLayer for Vec<T>
where
    T: Clone,
{
    fn apply_layer(&mut self, layer: &Self) {
        self.clone_from(layer);
    }
}

impl<K, V> ApplyLayer for IndexMap<K, V>
where
    K: Hash + Eq + Clone,
    V: ApplyLayer + Clone,
{
    fn apply_layer(&mut self, layer: &Self) {
        for (key, value) in layer {
            self.entry(key.clone())
                .and_modify(|target| target.apply_layer(value))
                .or_insert_with(|| value.clone());
        }
    }
}

impl<K, V> ApplyLayer for HashMap<K, V>
where
    K: Hash + Eq + Clone,
    V: ApplyLayer + Clone,
{
    fn apply_layer(&mut self, layer: &Self) {
        for (key, value) in layer {
            self.entry(key.clone())
                .and_modify(|target| target.apply_layer(value))
                .or_insert_with(|| value.clone());
        }
    }
}

impl ApplyLayer for bool {
    fn apply_layer(&mut self, layer: &Self) {
        *self = *layer;
    }
}

impl ApplyLayer for String {
    fn apply_layer(&mut self, layer: &Self) {
        self.clone_from(layer);
    }
}

#[cfg(test)]
mod tests {
    use similar_asserts::assert_eq;

    use crate::config::testing;

    use super::*;

    #[derive(Debug, Clone, PartialEq, Eq)]
    struct ScalarAndSequence {
        scalar: String,
        sequence: Vec<&'static str>,
    }

    impl ScalarAndSequence {
        fn new<Scalar, Sequence>(scalar: Scalar, sequence: Sequence) -> Self
        where
            Scalar: Into<String>,
            Sequence: Into<Vec<&'static str>>,
        {
            Self {
                scalar: scalar.into(),
                sequence: sequence.into(),
            }
        }
    }

    impl ApplyLayer for ScalarAndSequence {
        fn apply_layer(&mut self, layer: &Self) {
            self.scalar.clone_from(&layer.scalar);
            self.sequence.extend(&layer.sequence);
        }
    }

    #[test]
    fn scalar_values_apply_layer_replaces_target_value() {
        let mut scalar = "from target".to_owned();
        let layer_scalar = "from layer".to_owned();

        scalar.apply_layer(&layer_scalar);

        assert_eq!(scalar, "from layer".to_owned());

        let mut scalar = false;
        let layer_scalar = true;

        scalar.apply_layer(&layer_scalar);

        assert_eq!(scalar, true);
    }

    #[test]
    fn vec_apply_layer_replaces_target_value() {
        let mut sequence = vec!["target-1", "target-2"];
        let layer_sequence = vec!["layer-1", "layer-2"];

        sequence.apply_layer(&layer_sequence);

        assert_eq!(sequence, vec!["layer-1", "layer-2"]);
    }

    #[test]
    fn option_apply_layer_merges_inner_values_and_ignores_none() {
        let mut value = Some(ScalarAndSequence {
            scalar: "from target".to_owned(),
            sequence: vec!["target"],
        });
        let layer = Some(ScalarAndSequence::new("from layer", ["layer"]));
        value.apply_layer(&layer);
        assert_eq!(
            value,
            Some(ScalarAndSequence::new("from layer", ["target", "layer"]))
        );

        let mut existing = Some("from target".to_owned());
        existing.apply_layer(&None);
        assert_eq!(existing, Some("from target".to_owned()));

        let mut missing = None;
        let layer = Some("from layer".to_owned());
        missing.apply_layer(&layer);
        assert_eq!(missing, Some("from layer".to_owned()));
    }

    #[test]
    fn index_map_apply_layer_updates_values_without_reordering_keys() {
        let mut target = IndexMap::from([
            (
                "replaced",
                ScalarAndSequence::new("from target", ["target"]),
            ),
            (
                "untouched",
                ScalarAndSequence::new("keep target", ["keep target"]),
            ),
        ]);
        let layer = IndexMap::from([
            ("replaced", ScalarAndSequence::new("from layer", ["layer"])),
            (
                "inserted",
                ScalarAndSequence::new("only in layer", ["only in layer"]),
            ),
        ]);

        target.apply_layer(&layer);
        testing::assert_indexmap_eq(
            &target,
            [
                (
                    "replaced",
                    ScalarAndSequence::new("from layer", ["target", "layer"]),
                ),
                (
                    "untouched",
                    ScalarAndSequence::new("keep target", ["keep target"]),
                ),
                (
                    "inserted",
                    ScalarAndSequence::new("only in layer", ["only in layer"]),
                ),
            ],
        );
    }

    #[test]
    fn hash_map_apply_layer_updates_values_and_inserts_missing_keys() {
        let mut target =
            HashMap::from([("updated", ScalarAndSequence::new("from target", ["target"]))]);
        let layer = HashMap::from([
            ("updated", ScalarAndSequence::new("from layer", ["layer"])),
            (
                "inserted",
                ScalarAndSequence::new("only in layer", ["only in layer"]),
            ),
        ]);

        target.apply_layer(&layer);

        assert_eq!(
            target,
            HashMap::from([
                (
                    "updated",
                    ScalarAndSequence::new("from layer", ["target", "layer"]),
                ),
                (
                    "inserted",
                    ScalarAndSequence::new("only in layer", ["only in layer"]),
                ),
            ])
        );
    }
}