breb 0.5.0

the blog/reblog library and command-line tool
Documentation
use std::fmt;

macro_rules! render {
	($template:literal $(, $name:ident=$value:expr)* $(,)?) => {
		format!(include_str!($template), $($name=crate::live::escape::Escaper($value)),*)
	}
}
pub(crate) use render;

pub struct Escaper<T>(pub T);
impl<T: fmt::Display> fmt::Display for Escaper<T> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		use fmt::Write;
		write!(Escaper(f), "{}", self.0)
	}
}
impl<T: fmt::Debug> fmt::Debug for Escaper<T> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		use fmt::Write;
		write!(Escaper(f), "{:?}", self.0)
	}
}
impl<T: fmt::Write> fmt::Write for Escaper<T> {
	fn write_str(&mut self, s: &str) -> fmt::Result {
		const NEEDS_ESCAPE: [char; 3] = ['<', '>', '&'];
		const _: () = {
			let mut i = 0;
			while i < NEEDS_ESCAPE.len() {
				let c = NEEDS_ESCAPE[i];
				assert!(c.len_utf8() == 1, "multibyte char needs code updates");
				i += 1;
			}
		};

		for chunk in s.split_inclusive(NEEDS_ESCAPE) {
			if chunk.is_empty() {
				// should only happen right at the end --
				// otherwise we'd have at least the terminator
				break;
			}
			match chunk.strip_suffix(NEEDS_ESCAPE) {
				None => {
					// should also only happen right at the end
					self.0.write_str(chunk)?;
					break;
				}
				Some(safe) => {
					self.0.write_str(safe)?;
					// we know the final thing is one byte long, since everything in NEEDS_ESCAPE is,
					// so this lets us get the last byte way cheaper than iterating
					let esc = match chunk.as_bytes()[chunk.len() - 1] {
						b'<' => "&lt;",
						b'>' => "&gt;",
						b'&' => "&amp;",
						other => unreachable!("unhandled: {other:?}"),
					};
					self.0.write_str(esc)?;
				}
			}
		}
		Ok(())
	}
}