use std::{fs::read_to_string, io::Write as _, path::PathBuf, sync::mpsc}; use clap::{Parser, ValueEnum}; use notify::{RecursiveMode, Watcher}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; /// Watch a file and print diffs of changes. #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct Args { /// Path to the file to watch. file: PathBuf, /// Whether to enable color output. #[arg(long, value_enum, default_value_t = ColorMode::Auto)] color: ColorMode, /// Show unchanged lines. #[arg(long, default_value_t = false)] show_unchanged: bool, } #[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] enum ColorMode { Auto, Always, Never, } impl From for ColorChoice { fn from(value: ColorMode) -> Self { match value { ColorMode::Auto => Self::Auto, ColorMode::Always => Self::Always, ColorMode::Never => Self::Never, } } } fn main() -> anyhow::Result<()> { let args = Args::parse(); let (tx, rx) = mpsc::channel(); let mut stdout = StandardStream::stdout(args.color.into()); let mut watcher = notify::recommended_watcher(tx)?; watcher.watch(&args.file, RecursiveMode::NonRecursive)?; let mut old_content = read_to_string(&args.file)?; for evt in rx { let evt = evt?; match evt.kind { notify::EventKind::Create(_) | notify::EventKind::Modify(_) => { let new_content = read_to_string(&args.file)?; if old_content == new_content { // ignore continue; } on_update(&mut stdout, &old_content, &new_content, args.show_unchanged)?; old_content = new_content; } notify::EventKind::Remove(_) => { stdout.reset()?; writeln!(&mut stdout, "................ file deleted!")?; break; } _ => {} } } stdout.reset()?; Ok(()) } fn on_update( out: &mut impl WriteColor, old_content: &str, new_content: &str, show_unchanged: bool, ) -> std::io::Result<()> { let color_deleted = { let mut color = ColorSpec::new(); color.set_fg(Some(Color::Red)); color }; let color_unchanged = ColorSpec::new(); let color_added = { let mut color = ColorSpec::new(); color.set_fg(Some(Color::Green)); color }; let (mut left_line, mut right_line) = (0, 0); out.reset()?; writeln!(out, "................ file modified!")?; for line in diff::lines(old_content, new_content) { match line { diff::Result::Left(l) => { left_line += 1; out.set_color(&color_deleted)?; writeln!(out, "{left_line:>5} - {l}")?; } diff::Result::Both(l, _) => { left_line += 1; right_line += 1; if show_unchanged { out.set_color(&color_unchanged)?; writeln!(out, "{left_line:>5} {right_line:>5} | {l}")?; } } diff::Result::Right(l) => { right_line += 1; out.set_color(&color_added)?; writeln!(out, " {right_line:>5} + {l}")?; } } } out.reset()?; Ok(()) }