use colored::{Color, Colorize};
use std::fmt::Display;
const DEFAULT_LEFT_MARKER: char = '-';
const DEFAULT_RIGHT_MARKER: char = '+';
const DEFAULT_MARKER_COUNT: usize = 4;
const DEFAULT_INDENT_SPACES: usize = 2;
const DEFAULT_LEFT_COLOR: Color = Color::Green;
const DEFAULT_RIGHT_COLOR: Color = Color::Red;
use anyhow::Result;
fn parse_color(_s: &str) -> Result<Color> {
todo!();
}
#[derive(Debug)]
enum Side {
Left,
Right,
}
impl Display for Side {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Left => write!(f, "left"),
Self::Right => write!(f, "right"),
}
}
}
fn header(side: Side, name: Option<&String>, marker: char, marker_len: usize) -> String {
let marker_bar = marker.to_string().repeat(marker_len);
let name = match name {
Some(name) => {
let extra_space = match side {
Side::Left => " ", Side::Right => "",
};
format!("{side}:{extra_space} {name}")
}
None => format!("{side}"),
};
format!("{marker_bar} {name}")
}
enum ColorSide {
Left,
Right,
Both,
}
fn display_str(num: Option<usize>, max_width: Option<usize>) -> String {
match max_width {
Some(width) => match num {
Some(num) => format!("{num:>width$}"),
None => " ".repeat(width),
},
None => match num {
Some(num) => num.to_string(),
None => String::from(" "),
},
}
}
#[derive(Debug)]
pub enum Diff<'a, T> {
Same,
Diff {
settings: &'a DiffSettings,
diff: Vec<diff::Result<T>>,
},
}
impl<T> Display for Diff<'_, T>
where
T: Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Same => write!(f, "")?,
Self::Diff { settings, diff } => {
let max_num_width = settings.max_line_number.map(|x| x.ilog10() as usize + 1);
let left_color = if let Some(color) = settings.left_color {
color
} else {
DEFAULT_LEFT_COLOR
};
let right_color = if let Some(color) = settings.right_color {
color
} else {
DEFAULT_RIGHT_COLOR
};
let indent = " ".repeat(settings.indent_spaces);
if settings.force_color {
colored::control::set_override(true);
}
if settings.no_color {
colored::control::set_override(false);
}
let left_header = header(
Side::Left,
settings.left_name.as_ref(),
settings.left_marker,
settings.marker_count,
)
.color(left_color);
let right_header = header(
Side::Right,
settings.right_name.as_ref(),
settings.right_marker,
settings.marker_count,
)
.color(right_color);
writeln!(f, "{left_header}")?;
writeln!(f, "{right_header}")?;
let mut line_num_a = 0;
let mut line_num_b = 0;
for line in diff {
let (sep, content, line_num_a_display, line_num_b_display, color) = match line {
diff::Result::Left(l) => {
line_num_a += 1;
('-', l, Some(line_num_a), None, ColorSide::Left)
}
diff::Result::Both(l, _) => {
line_num_a += 1;
line_num_b += 1;
('|', l, Some(line_num_a), Some(line_num_b), ColorSide::Both)
}
diff::Result::Right(r) => {
line_num_b += 1;
('+', r, None, Some(line_num_b), ColorSide::Right)
}
};
let line_num_a_display = display_str(line_num_a_display, max_num_width);
let line_num_b_display = display_str(line_num_b_display, max_num_width);
let line = format!(
"{indent}{line_num_a_display}{indent}{line_num_b_display} {sep} {content}"
);
let line = match color {
ColorSide::Left => line.color(left_color),
ColorSide::Right => line.color(right_color),
ColorSide::Both => line.dimmed(),
};
writeln!(f, "{line}")?;
}
}
}
Ok(())
}
}
pub fn line_diff<'a>(
left: &'a str,
right: &'a str,
settings: &'a DiffSettings,
) -> Diff<'a, &'a str> {
let diff = diff::lines(left, right);
let mut same = true;
for line in &diff {
match line {
diff::Result::Left(_) => {
same = false;
break;
}
diff::Result::Both(_, _) => {
continue;
}
diff::Result::Right(_) => {
same = false;
break;
}
}
}
if same {
Diff::Same
} else {
Diff::Diff { settings, diff }
}
}
#[derive(Debug, Clone, clap::Parser)]
pub struct DiffSettings {
#[clap(long)]
left_name: Option<String>,
#[clap(long)]
right_name: Option<String>,
#[clap(long, default_value_t = DEFAULT_LEFT_MARKER)]
left_marker: char,
#[clap(long, default_value_t = DEFAULT_RIGHT_MARKER)]
right_marker: char,
#[clap(long, default_value_t = DEFAULT_MARKER_COUNT)]
marker_count: usize,
#[clap(long, default_value_t = DEFAULT_INDENT_SPACES)]
indent_spaces: usize,
#[clap(short, long)]
force_color: bool,
#[clap(long, value_parser = parse_color)]
left_color: Option<Color>,
#[clap(long, value_parser = parse_color)]
right_color: Option<Color>,
#[clap(long)]
no_color: bool,
#[clap(skip)]
max_line_number: Option<usize>,
}
impl DiffSettings {
pub fn new() -> Self {
Self::default()
}
pub fn names(mut self, left: String, right: String) -> Self {
self.left_name = Some(left);
self.right_name = Some(right);
self
}
pub fn max_line_number(mut self, n: usize) -> Self {
self.max_line_number = Some(n);
self
}
}
impl Default for DiffSettings {
fn default() -> Self {
Self {
left_name: None,
right_name: None,
left_marker: DEFAULT_LEFT_MARKER,
right_marker: DEFAULT_RIGHT_MARKER,
marker_count: DEFAULT_MARKER_COUNT,
indent_spaces: DEFAULT_INDENT_SPACES,
force_color: false,
left_color: Some(DEFAULT_LEFT_COLOR),
right_color: Some(DEFAULT_RIGHT_COLOR),
no_color: false,
max_line_number: None,
}
}
}