khtml 0.1.4

Simple macros for simple html generation
Documentation
use html_escape::encode_double_quoted_attribute_to_string;
pub use html_escape::encode_text_minimal_to_string as escape_inner;

pub trait Child: std::fmt::Display {}

impl<T: 'static> Child for T where T: std::fmt::Display {}

pub struct Tag {
    pub name: &'static str,
    pub attrs: Vec<(&'static str, Box<dyn std::fmt::Display>)>,
    pub children: Vec<Box<dyn Child>>,
}

impl Tag {
    pub fn with_children<V: Child + 'static, T: IntoIterator<Item = V>>(
        mut self,
        children: T,
    ) -> Self {
        self.children.clear();
        self.add_children(children)
    }

    pub fn add_children<V: Child + 'static, T: IntoIterator<Item = V>>(
        mut self,
        children: T,
    ) -> Self {
        self.children.extend(
            children
                .into_iter()
                .map(|v| -> Box<dyn Child> { Box::new(v) }),
        );
        self
    }
}

impl std::fmt::Display for Tag {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        use std::fmt::Write;

        write!(f, "<{}", self.name)?;
        let mut buf = String::new();
        let mut enbuf = String::new();
        for (k, v) in self.attrs.iter() {
            buf.clear();
            write!(&mut buf, "{}", v)?;
            if buf.is_empty() {
                continue;
            }
            enbuf.clear();
            encode_double_quoted_attribute_to_string(buf.as_str(), &mut enbuf);
            write!(f, " {}=\"{}\"", k, enbuf)?;
        }
        write!(f, ">")?;
        for child in self.children.iter() {
            // TODO:
            //  escape properly
            //    - ashkan, Wed 11 Nov 2020 01:05:34 AM JST
            write!(f, "{}", child)?;
        }
        write!(f, "</{}>", self.name)
        // match &self.children[..] {
        //     // [] => write!(f, "/>"),
        //     // [v] => {
        //     //     buf.clear();
        //     //     write!(&mut buf, "{}", v)?;
        //     //     if buf.is_empty() {
        //     //         return write!(f, "/>");
        //     //     }
        //     //     write!(f, ">{}</{}>", buf, self.name)
        //     // }
        //     children => {
        //         // TODO:
        //         //  use itertools .format()?
        //         //    - ashkan, Wed 07 Oct 2020 11:24:22 AM JST
        //         write!(f, ">")?;
        //         for child in children {
        //             // TODO:
        //             //  escape properly
        //             //    - ashkan, Wed 11 Nov 2020 01:05:34 AM JST
        //             write!(f, "{}", child)?;
        //         }
        //         write!(f, "</{}>", self.name)
        //     }
        // }
    }
}

#[macro_export]
macro_rules! tag {
  ($name:literal[$($k:literal=$v:expr)+] $($children:expr)*) => {
      $crate::Tag {
          name: $name,
          attrs: vec![$(($k, Box::new($v))),*],
          children: vec![$(Box::new($children)),*],
      }
  };
  ($name:literal $($children:expr)*) => {
      $crate::Tag {
          name: $name,
          attrs: vec![],
          children: vec![$(Box::new($children)),*],
      }
  };
}

#[macro_export]
macro_rules! _maybe {
    ($v:literal) => {
        Some($v)
    };
    ($v:expr) => {
        $v
    };
}

#[macro_export]
macro_rules! class {
  ($($v:expr),*) => {
      (&[$($crate::_maybe!($v)),*]).into_iter().flatten().join(" ")
  };
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(
            tag!("html" tag!("body" 123)).to_string().as_str(),
            "<html><body>123</body></html>"
        );
        assert_eq!(
            tag!("html" tag!("body" 123).add_children(vec![1,2,3].into_iter().map(|c| tag!("div" c))))
                .to_string()
                .as_str(),
            "<html><body>123<div>1</div><div>2</div><div>3</div></body></html>"
        );
    }
}