enum AttrType {
KV(String, String),
Bool(String),
Data(String, String),
BoolData(String)
}
pub struct Element {
tag: String,
classes: Vec<String>,
alst: Vec<AttrType>
}
impl Element {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn new(tag: impl ToString) -> Self {
Self {
tag: tag.to_string(),
classes: Vec::new(),
alst: Vec::new()
}
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn class(mut self, cls: impl ToString) -> Self {
self.class_r(cls);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn class_r(&mut self, cls: impl ToString) -> &mut Self {
self.classes.push(cls.to_string());
self
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn flag(mut self, key: impl ToString) -> Self {
self.flag_r(key);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn flag_r(&mut self, key: impl ToString) -> &mut Self {
self.alst.push(AttrType::Bool(key.to_string()));
self
}
#[must_use]
pub fn attr(mut self, key: impl ToString, value: impl AsRef<str>) -> Self {
self.attr_r(key, value);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn attr_r(
&mut self,
key: impl ToString,
value: impl AsRef<str>
) -> &mut Self {
let key = key.to_string();
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.alst.push(AttrType::KV(
key,
html_escape::encode_double_quoted_attribute(value.as_ref()).to_string()
));
self
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn data_attr(
mut self,
key: impl ToString,
value: impl AsRef<str>
) -> Self {
self.data_attr_r(key, value);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn data_attr_r(
&mut self,
key: impl ToString,
value: impl AsRef<str>
) -> &mut Self {
let key = key.to_string();
self.alst.push(AttrType::Data(
key,
html_escape::encode_double_quoted_attribute(value.as_ref()).to_string()
));
self
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn data_flag(mut self, key: impl ToString) -> Self {
self.data_flag_r(key);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn data_flag_r(&mut self, key: impl ToString) -> &mut Self {
self.alst.push(AttrType::BoolData(key.to_string()));
self
}
}
impl Element {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn raw_attr(mut self, key: impl ToString, value: impl ToString) -> Self {
self.raw_attr_r(key, value);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn raw_attr_r(
&mut self,
key: impl ToString,
value: impl ToString
) -> &mut Self {
let key = key.to_string();
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.alst.push(AttrType::KV(key, value.to_string()));
self
}
}
impl Element {
#[must_use]
pub fn map_if<F>(self, flag: bool, f: F) -> Self
where
F: FnOnce(Self) -> Self
{
if flag {
f(self)
} else {
self
}
}
#[must_use]
pub fn map_opt<T, F>(self, opt: Option<T>, f: F) -> Self
where
F: FnOnce(Self, T) -> Self
{
if let Some(o) = opt {
f(self, o)
} else {
self
}
}
pub fn mod_if<F>(&mut self, flag: bool, f: F) -> &mut Self
where
F: FnOnce(&mut Self)
{
if flag {
f(self);
}
self
}
pub fn mod_opt<T, F>(&mut self, opt: Option<T>, f: F) -> &mut Self
where
F: FnOnce(&mut Self, T)
{
if let Some(o) = opt {
f(self, o);
}
self
}
}
impl Element {
fn gen_attr_list(&self) -> Option<Vec<String>> {
if self.alst.is_empty() && self.classes.is_empty() {
None
} else {
let mut ret = Vec::new();
if !self.classes.is_empty() {
ret.push(format!(r#"class="{}""#, self.classes.join(" ")));
}
let it = self.alst.iter().map(|a| match a {
AttrType::KV(k, v) => {
format!(r#"{k}="{v}""#)
}
AttrType::Bool(a) => a.clone(),
AttrType::Data(k, v) => {
format!(r#"data-{k}="{v}""#)
}
AttrType::BoolData(a) => {
format!("data-{a}")
}
});
ret.extend(it);
Some(ret)
}
}
}
impl Element {
pub fn sub<F>(self, bldr: &mut sidoc::Builder, f: F)
where
F: FnOnce(&mut sidoc::Builder)
{
if let Some(lst) = self.gen_attr_list() {
bldr.scope(
format!("<{} {}>", self.tag, lst.join(" ")),
Some(format!("</{}>", self.tag))
);
} else {
let stag = format!("<{}>", self.tag);
let etag = format!("</{}>", self.tag);
bldr.scope(stag, Some(etag));
}
f(bldr);
bldr.exit();
}
}
impl Element {
#[inline]
pub fn add_empty(self, bldr: &mut sidoc::Builder) {
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>", self.tag, alst.join(" "))
} else {
format!("<{}>", self.tag)
};
bldr.line(line);
}
#[inline]
pub fn add_content(self, text: &str, bldr: &mut sidoc::Builder) {
let line = if let Some(alst) = self.gen_attr_list() {
format!(
"<{} {}>{}</{}>",
self.tag,
alst.join(" "),
html_escape::encode_text(text),
self.tag
)
} else {
format!(
"<{}>{}</{}>",
self.tag,
html_escape::encode_text(text),
self.tag
)
};
bldr.line(line);
}
#[inline]
pub fn add_raw_content(self, text: &str, bldr: &mut sidoc::Builder) {
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>{}</{}>", self.tag, alst.join(" "), text, self.tag)
} else {
format!("<{}>{}</{}>", self.tag, text, self.tag)
};
bldr.line(line);
}
pub fn add_scope(self, bldr: &mut sidoc::Builder) {
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>", self.tag, alst.join(" "))
} else {
format!("<{}>", self.tag)
};
bldr.scope(line, Some(format!("</{}>", self.tag)));
}
pub fn scope<F>(self, bldr: &mut sidoc::Builder, f: F)
where
F: FnOnce(&mut sidoc::Builder)
{
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>", self.tag, alst.join(" "))
} else {
format!("<{}>", self.tag)
};
bldr.scope(line, Some(format!("</{}>", self.tag)));
f(bldr);
bldr.exit();
}
}