yggdrasil-cli 0.2.5

Yggdrasil is a project flattener and diff engine that turns any subset of your codebase into a single AI-ready codex (index + contents), or compares snapshots with annotated diffs.
use std::fs;
use std::io::Write;
use colored::*;

use crate::types::GroupedMatches;
use crate::formatters::traits::DiffFormatter;

pub struct DiffCliFormatter {
    pub colored: bool,
    pub align_tags: bool,
}

impl DiffFormatter for DiffCliFormatter {
    fn print_preamble(&self, out: &mut dyn Write) {
        if self.colored {
            writeln!(out, "{}\n", "📦 Cross-file Diff Report".bright_magenta().bold()).unwrap();
        } else {
            writeln!(out, "📦 Cross-file Diff Report\n").unwrap();
        }
    }

    fn print_index(&self, groups: &Vec<GroupedMatches>, out: &mut dyn Write) {
        writeln!(out, "📄 File Pairs").unwrap();

        for g in groups {
            if self.colored {
                writeln!(
                    out,
                    "{} {} {}",
                    g.from_file.truecolor(0, 255, 255),
                    "→".bright_magenta(),
                    g.to_file.truecolor(0, 255, 255)
                ).unwrap();
            } else {
                writeln!(out, "- {} → {}", g.from_file, g.to_file).unwrap();
            }
        }

        writeln!(out).unwrap();
    }

    fn print_contents(&self, groups: &Vec<GroupedMatches>, out: &mut dyn Write) {
        for g in groups {
            if self.colored {
                writeln!(
                    out,
                    "{} {} {}",
                    g.from_file.truecolor(0, 255, 255),
                    "→".bright_magenta(),
                    g.to_file.truecolor(0, 255, 255)
                ).unwrap();
            } else {
                writeln!(out, "{} → {}", g.from_file, g.to_file).unwrap();
            }

            if let Ok(src) = fs::read_to_string(&g.from_file) {
                let lines: Vec<&str> = src.lines().collect();
                let max_len = lines.iter().map(|l| l.len()).max().unwrap_or(0);

                for (lineno, line) in lines.iter().enumerate() {
                    let mut tag = String::new();

                    for bwv in &g.blocks {
                        let m = &bwv.block;
                        let status = if bwv.is_addition { "[ADDED]" } else { "[MOVED]" };

                        if lineno >= m.from_range.0 && lineno < m.from_range.1 {
                            tag = format!(
                                " {} ({}–{} → {}–{})",
                                status,
                                m.from_range.0, m.from_range.1,
                                m.to_file, m.to_range.0
                            );
                            break;
                        }
                    }

                    if self.align_tags {
                        if self.colored {
                            writeln!(
                                out,
                                "{:>4} {:<width$}{}",
                                lineno + 1,
                                line,
                                tag.bright_yellow(),
                                width = max_len + 1
                            ).unwrap();
                        } else {
                            writeln!(
                                out,
                                "{:>4} {:<width$}{}",
                                lineno + 1,
                                line,
                                tag,
                                width = max_len + 1
                            ).unwrap();
                        }
                    } else {
                        if self.colored {
                            writeln!(
                                out,
                                "{:>4} {}{}",
                                lineno + 1,
                                line,
                                tag.bright_yellow()
                            ).unwrap();
                        } else {
                            writeln!(
                                out,
                                "{:>4} {}{}",
                                lineno + 1,
                                line,
                                tag
                            ).unwrap();
                        }
                    }
                }
            }

            writeln!(out).unwrap();
        }
    }
}