mod html;
pub use html::*;
use std::borrow::Cow;
use std::fmt::Write;
use std::sync::{Arc, Mutex, Weak};
pub struct Buffer {
ctx: Arc<Mutex<Ctx>>,
node: Node<'static>,
}
pub struct Node<'a> {
depth: usize,
ctx: Weak<Mutex<Ctx>>,
escaping: Escaping,
_phantom: std::marker::PhantomData<&'a ()>,
}
enum Escaping {
Raw,
Normal,
Safe,
}
pub struct Void<'a> {
ctx: Weak<Mutex<Ctx>>,
_phantom: std::marker::PhantomData<&'a ()>,
}
pub struct Comment<'a> {
ctx: Weak<Mutex<Ctx>>,
_phantom: std::marker::PhantomData<&'a ()>,
}
#[derive(Default)]
struct Ctx {
wtr: String,
stack: Vec<Cow<'static, str>>,
tag_open: Option<&'static str>,
}
impl Buffer {
pub fn new() -> Buffer {
Buffer::default()
}
pub fn finish(self) -> String {
let mutex = Arc::try_unwrap(self.ctx).ok().unwrap();
let mut ctx = mutex.into_inner().unwrap();
ctx.close_deeper_than(0);
ctx.wtr
}
}
impl Default for Buffer {
fn default() -> Buffer {
let ctx = Arc::new(Mutex::new(Ctx::default()));
let node = Node {
depth: 0,
ctx: Arc::downgrade(&ctx),
escaping: Escaping::Normal,
_phantom: std::marker::PhantomData,
};
Buffer { node, ctx }
}
}
impl std::ops::Deref for Buffer {
type Target = Node<'static>;
fn deref(&self) -> &Node<'static> {
&self.node
}
}
impl std::ops::DerefMut for Buffer {
fn deref_mut(&mut self) -> &mut Node<'static> {
&mut self.node
}
}
impl Ctx {
fn close_unclosed(&mut self) {
if let Some(closer) = self.tag_open.take() {
self.wtr.write_str(closer).unwrap();
}
}
fn close_deeper_than(&mut self, depth: usize) {
self.close_unclosed();
let to_pop = self.stack.len() - depth;
for _ in 0..to_pop {
if let Some(tag) = self.stack.pop() {
writeln!(self.wtr, "{:>w$}/{}>", "<", tag, w = self.stack.len() + 1).unwrap();
}
}
}
fn open(&mut self, tag: &str, depth: usize) {
self.close_deeper_than(depth);
write!(self.wtr, "{:>w$}{}", "<", tag, w = depth + 1).unwrap();
self.tag_open = Some(">\n");
}
fn open_comment(&mut self, depth: usize) {
self.close_deeper_than(depth);
write!(self.wtr, "{:>w$}!-- ", "<", w = depth + 1).unwrap();
self.tag_open = Some(" -->\n");
}
}
impl<'a> Node<'a> {
pub fn child<'b>(&'b mut self, tag: Cow<'static, str>) -> Node<'b> {
let ctx = self.ctx.upgrade().unwrap();
let mut ctx = ctx.lock().unwrap();
ctx.open(&tag, self.depth);
ctx.stack.push(tag);
Node {
depth: self.depth + 1,
ctx: self.ctx.clone(),
escaping: Escaping::Normal,
_phantom: std::marker::PhantomData,
}
}
pub fn void_child<'b>(&'b mut self, tag: Cow<'static, str>) -> Void<'b> {
let ctx = self.ctx.upgrade().unwrap();
let mut ctx = ctx.lock().unwrap();
ctx.open(&tag, self.depth);
Void {
ctx: self.ctx.clone(),
_phantom: std::marker::PhantomData,
}
}
pub fn comment<'b>(&'b mut self) -> Comment<'b> {
let ctx = self.ctx.upgrade().unwrap();
let mut ctx = ctx.lock().unwrap();
ctx.open_comment(self.depth);
Comment {
ctx: self.ctx.clone(),
_phantom: std::marker::PhantomData,
}
}
pub fn attr(self, attr: &str) -> Node<'a> {
let ctx = self.ctx.upgrade().unwrap();
let mut ctx = ctx.lock().unwrap();
if ctx.tag_open.is_some() {
write!(ctx.wtr, " {}", attr).unwrap();
}
self
}
pub fn raw(mut self) -> Node<'a> {
self.escaping = Escaping::Raw;
self
}
pub fn safe(mut self) -> Node<'a> {
self.escaping = Escaping::Safe;
self
}
}
impl<'a> Write for Node<'a> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
let mutex = self.ctx.upgrade().unwrap();
let mut ctx = mutex.lock().unwrap();
ctx.close_deeper_than(self.depth);
let s = match self.escaping {
Escaping::Raw => s.into(),
Escaping::Normal => html_escape::encode_text(s),
Escaping::Safe => html_escape::encode_safe(s),
};
ctx.wtr.write_str(&s)
}
}
impl<'a> Void<'a> {
pub fn attr(self, attr: &str) -> Void<'a> {
let ctx = self.ctx.upgrade().unwrap();
let mut ctx = ctx.lock().unwrap();
if ctx.tag_open.is_some() {
write!(ctx.wtr, " {}", attr).unwrap();
}
self
}
}
impl<'a> Write for Comment<'a> {
fn write_char(&mut self, c: char) -> std::fmt::Result {
let mutex = self.ctx.upgrade().unwrap();
let mut ctx = mutex.lock().unwrap();
ctx.wtr.write_char(c)
}
fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::fmt::Result {
let mutex = self.ctx.upgrade().unwrap();
let mut ctx = mutex.lock().unwrap();
ctx.wtr.write_fmt(args)
}
fn write_str(&mut self, s: &str) -> std::fmt::Result {
let mutex = self.ctx.upgrade().unwrap();
let mut ctx = mutex.lock().unwrap();
ctx.wtr.write_str(s)
}
}