use super::{
AnchorTarget, AttributeMap, Container, LinkLabel, ListItem, ListType, Module,
StyledContainer,
};
use std::borrow::Cow;
use std::num::NonZeroU32;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case", tag = "element", content = "data")]
pub enum Element<'t> {
Container(Container<'t>),
StyledContainer(StyledContainer<'t>),
Module(Module<'t>),
Text(Cow<'t, str>),
Raw(Cow<'t, str>),
Email(Cow<'t, str>),
Anchor {
elements: Vec<Element<'t>>,
attributes: AttributeMap<'t>,
target: AnchorTarget,
},
List {
#[serde(rename = "type")]
ltype: ListType,
items: Vec<ListItem<'t>>,
},
Link {
url: Cow<'t, str>,
label: LinkLabel<'t>,
target: AnchorTarget,
},
RadioButton {
name: Cow<'t, str>,
checked: bool,
attributes: AttributeMap<'t>,
},
CheckBox {
checked: bool,
attributes: AttributeMap<'t>,
},
#[serde(rename_all = "kebab-case")]
Collapsible {
elements: Vec<Element<'t>>,
attributes: AttributeMap<'t>,
start_open: bool,
show_text: Option<Cow<'t, str>>,
hide_text: Option<Cow<'t, str>>,
show_top: bool,
show_bottom: bool,
},
Color {
color: Cow<'t, str>,
elements: Vec<Element<'t>>,
},
Code {
contents: Cow<'t, str>,
language: Option<Cow<'t, str>>,
},
Html { contents: Cow<'t, str> },
Iframe {
url: Cow<'t, str>,
attributes: AttributeMap<'t>,
},
LineBreak,
LineBreaks(NonZeroU32),
HorizontalRule,
}
impl Element<'_> {
pub fn name(&self) -> &'static str {
match self {
Element::Container(container) => container.ctype().name(),
Element::StyledContainer(container) => container.ctype().name(),
Element::Module(module) => module.name(),
Element::Text(_) => "Text",
Element::Raw(_) => "Raw",
Element::Email(_) => "Email",
Element::Anchor { .. } => "Anchor",
Element::List { .. } => "List",
Element::Link { .. } => "Link",
Element::RadioButton { .. } => "RadioButton",
Element::CheckBox { .. } => "CheckBox",
Element::Collapsible { .. } => "Collapsible",
Element::Color { .. } => "Color",
Element::Code { .. } => "Code",
Element::Html { .. } => "HTML",
Element::Iframe { .. } => "Iframe",
Element::LineBreak => "LineBreak",
Element::LineBreaks { .. } => "LineBreaks",
Element::HorizontalRule => "HorizontalRule",
}
}
}
impl slog::Value for Element<'_> {
fn serialize(
&self,
_: &slog::Record,
key: slog::Key,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
serializer.emit_str(key, self.name())
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum Elements<'t> {
Multiple(Vec<Element<'t>>),
Single(Element<'t>),
None,
}
impl Elements<'_> {
#[inline]
pub fn is_empty(&self) -> bool {
match self {
Elements::Multiple(elements) => elements.is_empty(),
Elements::Single(_) => false,
Elements::None => true,
}
}
#[inline]
pub fn len(&self) -> usize {
match self {
Elements::Multiple(elements) => elements.len(),
Elements::Single(_) => 1,
Elements::None => 0,
}
}
}
impl<'t> From<Element<'t>> for Elements<'t> {
#[inline]
fn from(element: Element<'t>) -> Elements<'t> {
Elements::Single(element)
}
}
impl<'t> From<Option<Element<'t>>> for Elements<'t> {
#[inline]
fn from(element: Option<Element<'t>>) -> Elements<'t> {
match element {
Some(element) => Elements::Single(element),
None => Elements::None,
}
}
}
impl<'t> From<Vec<Element<'t>>> for Elements<'t> {
#[inline]
fn from(elements: Vec<Element<'t>>) -> Elements<'t> {
Elements::Multiple(elements)
}
}
impl<'t> IntoIterator for Elements<'t> {
type Item = Element<'t>;
type IntoIter = ElementsIterator<'t>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
match self {
Elements::None => ElementsIterator::None,
Elements::Single(element) => ElementsIterator::Single(Some(element)),
Elements::Multiple(mut elements) => {
elements.reverse();
ElementsIterator::Multiple(elements)
}
}
}
}
#[derive(Debug)]
pub enum ElementsIterator<'t> {
Multiple(Vec<Element<'t>>),
Single(Option<Element<'t>>),
None,
}
impl<'t> Iterator for ElementsIterator<'t> {
type Item = Element<'t>;
#[inline]
fn next(&mut self) -> Option<Element<'t>> {
match self {
ElementsIterator::Multiple(ref mut elements) => elements.pop(),
ElementsIterator::Single(ref mut element) => element.take(),
ElementsIterator::None => None,
}
}
}
#[test]
fn elements_iter() {
macro_rules! check {
($elements:expr, $expected:expr $(,)?) => {{
let actual: Vec<Element> = $elements.into_iter().collect();
let expected = $expected;
assert_eq!(
actual, expected,
"Actual element iteration doesn't match expected"
);
}};
}
check!(Elements::None, vec![]);
check!(Elements::Single(text!("a")), vec![text!("a")]);
check!(
Elements::Multiple(vec![]), vec![],
);
check!(
Elements::Multiple(vec![text!("a")]), vec![text!("a")],
);
check!(
Elements::Multiple(vec![text!("a"), text!("b")]),
vec![text!("a"), text!("b")],
);
check!(
Elements::Multiple(vec![text!("a"), text!("b"), text!("c")]),
vec![text!("a"), text!("b"), text!("c")],
);
}