cargo-sync-rdme 0.8.0

Cargo subcommand to synchronize README with crate documentation
use std::{fmt, io};

use similar::{ChangeTag, TextDiff};
use supports_color::Stream;

use crate::terminal::StreamInfo;

#[derive(Debug)]
struct Line(Option<usize>);

impl fmt::Display for Line {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.0 {
            None => write!(f, "    "),
            Some(idx) => write!(f, "{:>4}", idx + 1),
        }
    }
}

#[derive(Debug)]
struct DiffStyler {
    stream: StreamInfo,
}

impl DiffStyler {
    fn new(stream: StreamInfo) -> Self {
        Self { stream }
    }

    fn style(&self) -> console::Style {
        let s = console::Style::new();
        match self.stream.kind() {
            Stream::Stdout => s.for_stdout(),
            Stream::Stderr => s.for_stderr(),
        }
    }

    fn styled<D>(&self, val: D) -> console::StyledObject<D> {
        let s = console::style(val);
        match self.stream.kind() {
            Stream::Stdout => s.for_stdout(),
            Stream::Stderr => s.for_stderr(),
        }
    }
}

pub(crate) fn write_pretty_diff(stream: StreamInfo, old: &str, new: &str) -> Result<(), io::Error> {
    let styling = DiffStyler::new(stream);
    let diff = TextDiff::from_lines(old, new);

    let mut output: &mut dyn io::Write = match stream.kind() {
        Stream::Stdout => &mut io::stdout().lock(),
        Stream::Stderr => &mut io::stderr().lock(),
    };

    for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
        if idx > 0 {
            writeln!(&mut output, "{0:─^1$}{0:─^2$}", "", 9, 120)?;
        }
        for op in group {
            for change in diff.iter_inline_changes(op) {
                let (sign, style) = match change.tag() {
                    ChangeTag::Delete => ("-", styling.style().red()),
                    ChangeTag::Insert => ("+", styling.style().green()),
                    ChangeTag::Equal => (" ", styling.style().dim()),
                };
                write!(
                    &mut output,
                    "{}{}{}",
                    styling.styled(Line(change.old_index())).dim(),
                    styling.styled(Line(change.new_index())).dim(),
                    style.apply_to(sign).bold(),
                )?;
                for (emphasized, value) in change.iter_strings_lossy() {
                    if emphasized {
                        write!(
                            &mut output,
                            "{}",
                            style.apply_to(value).underlined().on_black()
                        )?;
                    } else {
                        write!(&mut output, "{}", style.apply_to(value))?;
                    }
                }
                if change.missing_newline() {
                    writeln!(&mut output)?;
                }
            }
        }
    }
    Ok(())
}