wlk 0.1.0

File-centric, event-sourced version control system with implicit branching
Documentation
use colored::*;
use similar::{ChangeTag, TextDiff};

/// Display a unified diff between two texts
pub fn show_diff(old: &str, new: &str, old_label: &str, new_label: &str, use_color: bool) {
    let diff = TextDiff::from_lines(old, new);

    if use_color {
        println!("{}", format!("--- {}", old_label).red());
        println!("{}", format!("+++ {}", new_label).green());
    } else {
        println!("--- {}", old_label);
        println!("+++ {}", new_label);
    }

    for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
        if idx > 0 {
            println!("...");
        }

        for op in group {
            for change in diff.iter_changes(op) {
                let sign = match change.tag() {
                    ChangeTag::Delete => "-",
                    ChangeTag::Insert => "+",
                    ChangeTag::Equal => " ",
                };

                if use_color {
                    match change.tag() {
                        ChangeTag::Delete => print!("{}", format!("{}{}", sign, change).red()),
                        ChangeTag::Insert => print!("{}", format!("{}{}", sign, change).green()),
                        ChangeTag::Equal => print!("{}{}", sign, change),
                    }
                } else {
                    print!("{}{}", sign, change);
                }
            }
        }
    }
}

/// Display a simple side-by-side comparison
pub fn show_simple_diff(old: &str, new: &str, use_color: bool) {
    let diff = TextDiff::from_lines(old, new);
    let mut changes = 0;

    for op in diff.ops() {
        for change in diff.iter_changes(op) {
            match change.tag() {
                ChangeTag::Delete => {
                    if use_color {
                        print!("{}", format!("- {}", change.value()).red());
                    } else {
                        print!("- {}", change.value());
                    }
                    changes += 1;
                }
                ChangeTag::Insert => {
                    if use_color {
                        print!("{}", format!("+ {}", change.value()).green());
                    } else {
                        print!("+ {}", change.value());
                    }
                    changes += 1;
                }
                ChangeTag::Equal => {}
            }
        }
    }

    if changes == 0 {
        println!("No differences found.");
    }
}

/// Get diff statistics
pub fn diff_stats(old: &str, new: &str) -> DiffStats {
    let diff = TextDiff::from_lines(old, new);
    let mut stats = DiffStats::default();

    for op in diff.ops() {
        for change in diff.iter_changes(op) {
            match change.tag() {
                ChangeTag::Delete => {
                    stats.deletions += 1;
                    stats.lines_removed += 1;
                }
                ChangeTag::Insert => {
                    stats.insertions += 1;
                    stats.lines_added += 1;
                }
                ChangeTag::Equal => {}
            }
        }
    }

    stats
}

#[derive(Debug, Default)]
pub struct DiffStats {
    pub insertions: usize,
    pub deletions: usize,
    pub lines_added: usize,
    pub lines_removed: usize,
}

impl DiffStats {
    pub fn display(&self, use_color: bool) {
        if use_color {
            println!(
                "{} insertions({}), {} deletions({})",
                self.insertions,
                "+".green(),
                self.deletions,
                "-".red()
            );
        } else {
            println!(
                "{} insertions(+), {} deletions(-)",
                self.insertions, self.deletions
            );
        }
    }
}

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

    #[test]
    fn test_diff_stats() {
        let old = "line1\nline2\nline3\n";
        let new = "line1\nline2 modified\nline4\n";

        let stats = diff_stats(old, new);
        assert!(stats.insertions > 0 || stats.deletions > 0);
    }

    #[test]
    fn test_no_diff() {
        let text = "same\ncontent\n";
        let stats = diff_stats(text, text);
        assert_eq!(stats.insertions, 0);
        assert_eq!(stats.deletions, 0);
    }
}