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() {
break;
}
match chunk.strip_suffix(NEEDS_ESCAPE) {
None => {
self.0.write_str(chunk)?;
break;
}
Some(safe) => {
self.0.write_str(safe)?;
let esc = match chunk.as_bytes()[chunk.len() - 1] {
b'<' => "<",
b'>' => ">",
b'&' => "&",
other => unreachable!("unhandled: {other:?}"),
};
self.0.write_str(esc)?;
}
}
}
Ok(())
}
}