assert2 0.3.8

assert!(...) and check!(...) macros inspired by Catch2
Documentation
use atty::Stream;
use std::fmt::Debug;
use yansi::Paint;
use std::fmt::Write;

fn should_color() -> bool {
	if std::env::var_os("CLICOLOR").map(|x| x == "0").unwrap_or(false) {
		false
	} else if std::env::var_os("CLICOLOR_FORCE").map(|x| x != "0").unwrap_or(false) {
		true
	} else {
		atty::is(Stream::Stderr)
	}
}

fn set_color() {
	if should_color() {
		Paint::enable()
	} else {
		Paint::disable()
	}
}

pub struct FailedCheck<'a, T> {
	pub macro_name: &'a str,
	pub file: &'a str,
	pub line: u32,
	pub column: u32,
	pub custom_msg: Option<std::fmt::Arguments<'a>>,
	pub expression: T,
	pub fragments: &'a [(&'a str, &'a str)],
}

pub trait CheckExpression {
	fn write_expression(&self, buffer: &mut  String);
	fn write_expansion(&self, buffer: &mut String);
}

pub struct BinaryOp<'a, Left, Right> {
	pub left: &'a Left,
	pub right: &'a Right,
	pub operator: &'a str,
	pub left_expr: &'a str,
	pub right_expr: &'a str,
}

pub struct BooleanExpr<'a> {
	pub expression: &'a str,
}

pub struct MatchExpr<'a, Value> {
	pub print_let: bool,
	pub value: &'a Value,
	pub pattern: &'a str,
	pub expression: &'a str,
}

impl<'a, T: CheckExpression> FailedCheck<'a, T> {
	#[rustfmt::skip]
	pub fn print(&self) {
		set_color();
		let mut print_message = String::new();
		writeln!(&mut print_message, "{msg} at {file}:{line}:{column}:",
			msg    = Paint::red("Assertion failed").bold(),
			file   = Paint::default(self.file).bold(),
			line   = self.line,
			column = self.column,
		).unwrap();
		write!(&mut print_message, "  {name}{open} ",
			name = Paint::magenta(self.macro_name),
			open = Paint::magenta("!("),
		).unwrap();
		self.expression.write_expression(&mut print_message);
		writeln!(&mut print_message, " {}", Paint::magenta(")")).unwrap();
		if !self.fragments.is_empty() {
			writeln!(&mut print_message, "with:").unwrap();
			for (name, expansion) in self.fragments {
				writeln!(
					&mut print_message,
					"  {} {} {}",
					Paint::magenta(name), Paint::blue("=").bold(),
					expansion
				).unwrap();
			}
		}
		writeln!(&mut print_message, "with expansion:").unwrap();
		write!(&mut print_message, "  ").unwrap();
		self.expression.write_expansion(&mut print_message);
		writeln!(&mut print_message, ).unwrap();
		if let Some(msg) = self.custom_msg {
			writeln!(&mut print_message, "with message:").unwrap();
			writeln!(&mut print_message, "  {}", Paint::default(msg).bold()).unwrap();
		}
		writeln!(&mut print_message).unwrap();

		eprint!("{}", print_message);
	}
}

#[rustfmt::skip]
impl<Left: Debug, Right: Debug> CheckExpression for BinaryOp<'_, Left, Right> {
	fn write_expression(&self, buffer: &mut  String) {
		write!(buffer, "{left} {op} {right}",
			left  = Paint::cyan(self.left_expr),
			op    = Paint::blue(self.operator).bold(),
			right = Paint::yellow(self.right_expr),
		).unwrap();
	}
	fn write_expansion(&self, buffer: &mut  String) {
		write!(buffer, "{left:?} {op} {right:?}",
			left  = Paint::cyan(self.left),
			op    = Paint::blue(self.operator).bold(),
			right = Paint::yellow(self.right),
		).unwrap();
	}
}

#[rustfmt::skip]
impl CheckExpression for BooleanExpr<'_> {
	fn write_expression(&self, print_message: &mut  String) {
		write!(print_message, "{}", Paint::cyan(self.expression)).unwrap();
	}
	fn write_expansion(&self, print_message: &mut String) {
		write!(print_message, "{:?}", Paint::cyan(false)).unwrap();
	}
}

#[rustfmt::skip]
impl<Value: Debug> CheckExpression for MatchExpr<'_, Value> {
	fn write_expression(&self, buffer: &mut String) {
		if self.print_let {
			write!(buffer, "{} ", Paint::blue("let").bold()).unwrap();
		}
		write!(buffer, "{pat} {eq} {expr}",
			pat  = Paint::cyan(self.pattern),
			eq   = Paint::blue("=").bold(),
			expr = Paint::yellow(self.expression),
		).unwrap();
	}
	fn write_expansion(&self, print_message: &mut String) {
		write!(print_message, "{:?}", Paint::yellow(self.value)).unwrap();
	}
}