mod html;
pub use html::*;
use std::borrow::Cow;
use std::fmt::Write;
use std::sync::{Arc, Mutex, Weak};
#[derive(Clone)]
pub struct Document {
ctx: Arc<Mutex<Ctx>>,
node: Node<'static>,
}
#[derive(Clone)]
pub struct Node<'a> {
depth: usize,
ctx: Weak<Mutex<Ctx>>,
_phantom: std::marker::PhantomData<&'a ()>,
}
#[derive(Clone)]
pub struct Void<'a> {
ctx: Weak<Mutex<Ctx>>,
_phantom: std::marker::PhantomData<&'a ()>,
}
#[derive(Default)]
struct Ctx {
wtr: String,
stack: Vec<Cow<'static, str>>,
tag_open: bool,
}
impl Document {
pub fn new() -> Document {
let ctx = Arc::new(Mutex::new(Ctx::default()));
let node = Node {
depth: 0,
ctx: Arc::downgrade(&ctx),
_phantom: std::marker::PhantomData,
};
Document { node, ctx }
}
pub fn build(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 std::ops::Deref for Document {
type Target = Node<'static>;
fn deref(&self) -> &Node<'static> {
&self.node
}
}
impl std::ops::DerefMut for Document {
fn deref_mut(&mut self) -> &mut Node<'static> {
&mut self.node
}
}
impl Ctx {
fn close_unclosed(&mut self) {
if self.tag_open {
self.tag_open = false;
self.wtr.write_str(">\n").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() {
write!(self.wtr, "{:>w$}/{}>\n", "<", 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 = true;
}
}
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(),
_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 attr(self, attr: &str) -> Node<'a> {
let ctx = self.ctx.upgrade().unwrap();
let mut ctx = ctx.lock().unwrap();
if ctx.tag_open {
write!(ctx.wtr, " {}", attr).unwrap();
}
self
}
}
impl<'a> Write for Node<'a> {
fn write_char(&mut self, c: char) -> std::fmt::Result {
let mutex = self.ctx.upgrade().unwrap();
let mut ctx = mutex.lock().unwrap();
ctx.close_deeper_than(self.depth);
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.close_deeper_than(self.depth);
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.close_deeper_than(self.depth);
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 {
write!(ctx.wtr, " {}", attr).unwrap();
}
self
}
}
impl<'a> Write for Void<'a> {
fn write_char(&mut self, c: char) -> std::fmt::Result {
let mutex = self.ctx.upgrade().unwrap();
let mut ctx = mutex.lock().unwrap();
ctx.close_unclosed();
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.close_unclosed();
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.close_unclosed();
ctx.wtr.write_str(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
const EXPECTED: &str = "\
<html>
<head>
<title>
Foobar
</title>
</head>
<body>
Lorem ipsum
</body>
</html>
";
#[test]
fn full() {
let mut root = Document::new();
let mut html = root.child("html".into());
let mut head = html.child("head".into());
let mut title = head.child("title".into());
writeln!(title, "Foobar").unwrap();
let mut body = html.child("body".into());
writeln!(body, "Lorem ipsum").unwrap();
assert_eq!(&root.build(), EXPECTED);
}
#[test]
fn elided() {
let mut root = Document::new();
let mut html = root.child("html".into());
writeln!(html.child("head".into()).child("title".into()), "Foobar").unwrap();
writeln!(html.child("body".into()), "Lorem ipsum").unwrap();
assert_eq!(&root.build(), EXPECTED);
}
#[test]
fn pre_post_inner() {
let mut doc = Document::new();
let mut a = doc.child("a".into());
writeln!(a, "a pre").unwrap();
let mut b = a.child("b".into());
writeln!(b, "b pre").unwrap();
let mut c = b.child("c".into());
writeln!(c, "c pre").unwrap();
writeln!(c, "c post").unwrap();
writeln!(b, "b post").unwrap();
writeln!(a, "a post").unwrap();
assert_eq!(
doc.build(),
"\
<a>
a pre
<b>
b pre
<c>
c pre
c post
</c>
b post
</b>
a post
</a>
"
);
}
}