diffo 0.2.0

Semantic diffing for Rust structs via serde
Documentation
use super::Formatter;
use crate::{Diff, FormatError};

/// Pretty formatter for human-readable colored terminal output.
///
/// # Examples
///
/// ```
/// use diffo::{diff, format::PrettyFormatter, format::Formatter};
///
/// let old = vec![1, 2, 3];
/// let new = vec![1, 2, 4];
///
/// let d = diff(&old, &new).unwrap();
/// let formatter = PrettyFormatter::new();
/// println!("{}", formatter.format(&d).unwrap());
/// ```
#[derive(Debug)]
pub struct PrettyFormatter {
    use_colors: bool,
    indent: usize,
}

impl PrettyFormatter {
    /// Create a new pretty formatter with default settings.
    pub fn new() -> Self {
        Self {
            use_colors: cfg!(feature = "pretty"),
            indent: 2,
        }
    }

    /// Enable or disable colors.
    pub fn with_colors(mut self, enabled: bool) -> Self {
        self.use_colors = enabled;
        self
    }

    /// Set the indentation level.
    pub fn with_indent(mut self, spaces: usize) -> Self {
        self.indent = spaces;
        self
    }
}

impl Default for PrettyFormatter {
    fn default() -> Self {
        Self::new()
    }
}

impl Formatter for PrettyFormatter {
    fn format(&self, diff: &Diff) -> Result<String, FormatError> {
        if diff.is_empty() {
            return Ok(String::from("No changes"));
        }

        let mut output = String::new();

        for (path, change) in diff.changes() {
            use crate::Change;

            match change {
                Change::Added(val) => {
                    output.push_str(&format!("+ {}: {:?}\n", path, val));
                }
                Change::Removed(val) => {
                    output.push_str(&format!("- {}: {:?}\n", path, val));
                }
                Change::Modified { from, to } => {
                    output.push_str(&format!("{}\n", path));
                    output.push_str(&format!("  - {:?}\n", from));
                    output.push_str(&format!("  + {:?}\n", to));
                }
                Change::Elided { reason, count } => {
                    output.push_str(&format!("~ {}: {} ({} items)\n", path, reason, count));
                }
            }
        }

        Ok(output)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Change, Path};
    use serde_value::Value;

    #[test]
    fn test_empty_diff() {
        let diff = Diff::new();
        let formatter = PrettyFormatter::new();
        let output = formatter.format(&diff).unwrap();
        assert_eq!(output, "No changes");
    }

    #[test]
    fn test_added() {
        let mut diff = Diff::new();
        diff.insert(Path::root().field("x"), Change::Added(Value::I64(42)));

        let formatter = PrettyFormatter::new();
        let output = formatter.format(&diff).unwrap();
        assert!(output.contains("+ x:"));
    }
}