sync_readme_test_runner/
main.rs

1//! A very small binary which diffs the input with the rendered output and fails if they are different.
2//! Which allows this to be checked via a bazel test.
3
4use std::fmt;
5
6use anyhow::Context;
7use camino::Utf8PathBuf;
8use clap::Parser;
9use console::{Style, style};
10use similar::{ChangeTag, TextDiff};
11
12/// Simple program to greet a person
13#[derive(Parser, Debug)]
14#[command(version, about, long_about = None)]
15struct Args {
16    #[arg(long, env = "RENDER_OUTPUT_PATH")]
17    render_output: Utf8PathBuf,
18}
19
20struct Line(Option<usize>);
21
22impl fmt::Display for Line {
23    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24        match self.0 {
25            None => write!(f, "    "),
26            Some(idx) => write!(f, "{:<4}", idx + 1),
27        }
28    }
29}
30
31fn main() -> anyhow::Result<()> {
32    let args = Args::parse();
33
34    let render_output = std::fs::read_to_string(&args.render_output).context("read results")?;
35    let render_output: sync_readme_common::SyncReadmeRenderOutput =
36        serde_json::from_str(&render_output).context("parse results")?;
37    if render_output.rendered == render_output.source {
38        println!("readme matches render");
39        return Ok(());
40    }
41
42    println!("Difference found in {}", render_output.path);
43
44    println!("{}", diff(&render_output.source, &render_output.rendered));
45
46    std::process::exit(1)
47}
48
49pub(crate) fn diff(old: &str, new: &str) -> String {
50    use std::fmt::Write;
51
52    let diff = TextDiff::from_lines(old, new);
53    let mut output = String::new();
54
55    for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
56        if idx > 0 {
57            writeln!(&mut output, "{0:─^1$}┼{0:─^2$}", "─", 9, 120).unwrap();
58        }
59        for op in group {
60            for change in diff.iter_inline_changes(op) {
61                let (sign, s) = match change.tag() {
62                    ChangeTag::Delete => ("-", Style::new().red()),
63                    ChangeTag::Insert => ("+", Style::new().green()),
64                    ChangeTag::Equal => (" ", Style::new().dim()),
65                };
66                write!(
67                    &mut output,
68                    "{}{} │{}",
69                    style(Line(change.old_index())).dim(),
70                    style(Line(change.new_index())).dim(),
71                    s.apply_to(sign).bold(),
72                )
73                .unwrap();
74                for (_, value) in change.iter_strings_lossy() {
75                    write!(&mut output, "{}", s.apply_to(value)).unwrap();
76                }
77                if change.missing_newline() {
78                    writeln!(&mut output).unwrap();
79                }
80            }
81        }
82    }
83
84    output
85}