use implicit_clone::unsync::*;
use indexmap::IndexMap;
use std::fmt;
use std::rc::Rc;
pub enum VNode {
Tagged {
tag: &'static str,
children: Rc<[VNode]>,
attrs: IMap<IString, IString>,
classes: IArray<IString>,
},
Text(IString),
Fragment(Rc<[VNode]>),
Component(Rc<dyn Component>),
}
impl From<String> for VNode {
fn from(s: String) -> VNode {
VNode::Text(s.into())
}
}
impl From<&'static str> for VNode {
fn from(s: &'static str) -> VNode {
VNode::Text(s.into())
}
}
impl From<std::fmt::Arguments<'_>> for VNode {
fn from(args: std::fmt::Arguments) -> VNode {
VNode::Text(args.into())
}
}
impl<const N: usize> From<[VNode; N]> for VNode {
fn from(elements: [VNode; N]) -> VNode {
VNode::Fragment(elements.into())
}
}
impl VNode {
pub fn builder(tag: &'static str) -> VNodeBuilder {
VNodeBuilder::new(tag)
}
}
pub struct VNodeBuilder {
tag: &'static str,
class: Vec<IString>,
dyn_attrs: IndexMap<IString, IString>,
children: Vec<VNode>,
}
impl VNodeBuilder {
pub fn new(tag: &'static str) -> Self {
Self {
tag,
class: Default::default(),
dyn_attrs: Default::default(),
children: Default::default(),
}
}
pub fn set_attr_class(&mut self, class: impl Into<IString>) -> &mut Self {
self.add_attr_class(class, 1)
}
pub fn add_attr_class(&mut self, class: impl Into<IString>, additional: usize) -> &mut Self {
self.class.reserve_exact(additional);
self.class.push(class.into());
self
}
pub fn set_attr_style(&mut self, style: impl Into<IString>) -> &mut Self {
self.add_attr("style", style, 1)
}
pub fn add_child(&mut self, element: impl Into<VNode>, additional: usize) -> &mut Self {
self.children.reserve_exact(additional);
self.children.push(element.into());
self
}
pub fn add_attr(
&mut self,
name: impl Into<IString>,
value: impl Into<IString>,
additional: usize,
) -> &mut Self {
self.dyn_attrs.reserve(additional);
self.dyn_attrs.insert(name.into(), value.into());
self
}
pub fn finish(&mut self) -> VNode {
let children = Rc::from(std::mem::take(&mut self.children));
let dyn_attrs = Rc::from(std::mem::take(&mut self.dyn_attrs));
let class = Rc::from(std::mem::take(&mut self.class));
if self.tag == "" {
VNode::Fragment(children)
} else {
VNode::Tagged {
tag: self.tag,
children,
attrs: IMap::from(dyn_attrs),
classes: IArray::from(class),
}
}
}
}
impl fmt::Display for VNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Text(s) => {
write!(f, "{s}")?;
}
Self::Tagged {
tag,
children,
attrs,
classes,
} => {
write!(f, "<{tag}")?;
if !classes.is_empty() {
write!(f, " class=\"")?;
let mut it = classes.iter();
escape_attr_quoted_fmt(f, &it.next().unwrap())?;
for class in it {
write!(f, " ")?;
escape_attr_quoted_fmt(f, &class)?;
}
write!(f, "\"",)?;
}
for (attr_name, attr_value) in attrs.iter() {
write!(f, " {attr_name}=\"")?;
escape_attr_quoted_fmt(f, &attr_value)?;
write!(f, "\"")?;
}
write!(f, ">")?;
for child in children.iter() {
write!(f, "{child}")?;
}
write!(f, "</{tag}>")?;
}
Self::Fragment(children) => {
for child in children.iter() {
write!(f, "{child}")?;
}
}
Self::Component(comp) => {
write!(f, "{comp}")?;
}
}
Ok(())
}
}
pub trait Component: fmt::Display {}
pub struct MyComponent<T = ()> {
phantom: std::marker::PhantomData<T>,
}
impl<T> MyComponent<T> {
pub fn builder(_tag: &'static str) -> MyComponentBuilder<T> {
MyComponentBuilder {
phantom: std::marker::PhantomData,
}
}
}
pub struct MyComponentBuilder<T> {
phantom: std::marker::PhantomData<T>,
}
impl<T: 'static> MyComponentBuilder<T> {
pub fn finish(&mut self) -> VNode {
VNode::Component(Rc::new(MyComponentProps {
phantom: std::marker::PhantomData::<T>,
}))
}
}
pub struct MyComponentProps<T> {
phantom: std::marker::PhantomData<T>,
}
impl<T> fmt::Display for MyComponentProps<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "My custom component")
}
}
impl<T> Component for MyComponentProps<T> {}
impl<T: 'static> From<MyComponentProps<T>> for VNode {
fn from(component: MyComponentProps<T>) -> VNode {
VNode::Component(Rc::new(component))
}
}
fn escape_attr_quoted_fmt<W: fmt::Write>(w: &mut W, s: &str) -> fmt::Result {
for ch in s.chars() {
match ch {
'&' => w.write_str("&")?,
'"' => w.write_str(""")?,
'<' => w.write_str("<")?,
'>' => w.write_str(">")?,
_ => w.write_char(ch)?,
}
}
Ok(())
}
#[doc(hidden)]
#[allow(non_camel_case_types)]
pub mod html_context {
pub type h1 = super::VNode;
pub type div = super::VNode;
pub type span = super::VNode;
pub type br = super::VNode;
pub type Text = super::VNode;
pub type Fragment = super::VNode;
pub use super::MyComponent;
}
pub mod prelude {
pub use super::{html_context, VNode};
pub use yo_html::html;
}