1use std::{borrow::Cow, fmt::Display};
2
3pub use self::attribute::{
4 Attribute, AttributeName, AttributeValue, InvalidAttributeName, InvalidAttributeValue,
5};
6pub use self::name::{BlockName, InvalidBlockName};
7
8mod attribute;
9mod name;
10
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Block<'a> {
16 pub name: BlockName<'a>,
17 pub attributes: Vec<Attribute<'a>>,
18 pub content: Cow<'a, str>,
19}
20
21impl Display for Block<'_> {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 let Self {
24 name,
25 attributes,
26 content,
27 } = self;
28
29 let content = content.trim_end();
30 write!(f, "<{name}")?;
31
32 for (name, value) in attributes {
33 match value {
34 Some(value) if value.as_str().contains('\u{0022}') => {
35 write!(f, " {name}='{value}'")?;
36 }
37 Some(value) => {
38 write!(f, r#" {name}="{value}""#)?;
39 }
40 None => {
41 write!(f, " {name}")?;
42 }
43 }
44 }
45
46 write!(f, ">")?;
47
48 if !content.is_empty() {
49 writeln!(f)?;
50 writeln!(f, "{content}")?;
51 }
52
53 write!(f, "</{name}>")
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use std::borrow::Cow;
60
61 use super::{AttributeName, AttributeValue, Block, BlockName};
62
63 #[test]
64 fn test_display() {
65 assert_eq!(
66 Block {
67 name: BlockName::try_from("template").unwrap(),
68 attributes: Vec::new(),
69 content: Cow::Borrowed("")
70 }
71 .to_string(),
72 "<template></template>"
73 );
74
75 assert_eq!(
76 Block {
77 name: BlockName::try_from("script").unwrap(),
78 attributes: vec![(
79 AttributeName::try_from("lang").unwrap(),
80 Some(AttributeValue::try_from("ts").unwrap())
81 )],
82 content: Cow::Borrowed("")
83 }
84 .to_string(),
85 r#"<script lang="ts"></script>"#
86 );
87
88 assert_eq!(
89 Block {
90 name: BlockName::try_from("script").unwrap(),
91 attributes: vec![
92 (
93 AttributeName::try_from("lang").unwrap(),
94 Some(AttributeValue::try_from("ts").unwrap())
95 ),
96 (AttributeName::try_from("setup").unwrap(), None)
97 ],
98 content: Cow::Borrowed("")
99 }
100 .to_string(),
101 r#"<script lang="ts" setup></script>"#
102 );
103
104 assert_eq!(
105 Block {
106 name: BlockName::try_from("style").unwrap(),
107 attributes: vec![(AttributeName::try_from("scoped").unwrap(), None)],
108 content: Cow::Borrowed("")
109 }
110 .to_string(),
111 r#"<style scoped></style>"#
112 );
113
114 assert_eq!(
115 Block {
116 name: BlockName::try_from("template").unwrap(),
117 attributes: Vec::new(),
118 content: Cow::Borrowed("<!-- content -->")
119 }
120 .to_string(),
121 concat!("<template>\n", "<!-- content -->\n", "</template>")
122 );
123
124 assert_eq!(
125 Block {
126 name: BlockName::try_from("template").unwrap(),
127 attributes: Vec::new(),
128 content: Cow::Borrowed("<!-- multiline -->\n<!-- content -->")
129 }
130 .to_string(),
131 concat!(
132 "<template>\n",
133 "<!-- multiline -->\n",
134 "<!-- content -->\n",
135 "</template>"
136 )
137 );
138 }
139}