1use alloc::string::{String, ToString};
8
9use crate::{corety::AzString, props::formatter::PrintAsCssValue};
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[repr(C)]
21pub struct Content {
22 pub inner: AzString,
23}
24
25impl Default for Content {
26 fn default() -> Self {
27 Self {
28 inner: "normal".into(),
29 }
30 }
31}
32
33impl PrintAsCssValue for Content {
34 fn print_as_css_value(&self) -> String {
35 self.inner.as_str().to_string()
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41#[repr(C)]
42pub struct CounterReset {
43 pub counter_name: AzString,
44 pub value: i32,
45}
46
47impl CounterReset {
48 #[must_use] pub const fn new(counter_name: AzString, value: i32) -> Self {
49 Self {
50 counter_name,
51 value,
52 }
53 }
54
55 #[must_use] pub const fn none() -> Self {
56 Self {
57 counter_name: AzString::from_const_str("none"),
58 value: 0,
59 }
60 }
61
62 #[must_use] pub const fn list_item() -> Self {
63 Self {
64 counter_name: AzString::from_const_str("list-item"),
65 value: 0,
66 }
67 }
68}
69
70impl Default for CounterReset {
71 fn default() -> Self {
72 Self::none()
73 }
74}
75
76impl PrintAsCssValue for CounterReset {
77 fn print_as_css_value(&self) -> String {
78 if self.counter_name.as_str() == "none" {
79 "none".to_string()
80 } else {
81 alloc::format!("{} {}", self.counter_name.as_str(), self.value)
82 }
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88#[repr(C)]
89pub struct CounterIncrement {
90 pub counter_name: AzString,
91 pub value: i32,
92}
93
94impl CounterIncrement {
95 #[must_use] pub const fn new(counter_name: AzString, value: i32) -> Self {
96 Self {
97 counter_name,
98 value,
99 }
100 }
101
102 #[must_use] pub const fn none() -> Self {
103 Self {
104 counter_name: AzString::from_const_str("none"),
105 value: 0,
106 }
107 }
108
109 #[must_use] pub const fn list_item() -> Self {
110 Self {
111 counter_name: AzString::from_const_str("list-item"),
112 value: 1,
113 }
114 }
115}
116
117impl Default for CounterIncrement {
118 fn default() -> Self {
119 Self::none()
120 }
121}
122
123impl PrintAsCssValue for CounterIncrement {
124 fn print_as_css_value(&self) -> String {
125 if self.counter_name.as_str() == "none" {
126 "none".to_string()
127 } else {
128 alloc::format!("{} {}", self.counter_name.as_str(), self.value)
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
137#[repr(C)]
138pub struct StringSet {
139 pub inner: AzString,
140}
141
142impl Default for StringSet {
143 fn default() -> Self {
144 Self {
145 inner: "none".into(),
146 }
147 }
148}
149
150impl PrintAsCssValue for StringSet {
151 fn print_as_css_value(&self) -> String {
152 self.inner.as_str().to_string()
153 }
154}
155
156impl crate::codegen::format::FormatAsRustCode for Content {
158 fn format_as_rust_code(&self, _tabs: usize) -> String {
159 format!("Content {{ inner: String::from({:?}) }}", self.inner)
160 }
161}
162
163impl crate::codegen::format::FormatAsRustCode for CounterReset {
164 fn format_as_rust_code(&self, _tabs: usize) -> String {
165 alloc::format!(
166 "CounterReset {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
167 self.counter_name.as_str(),
168 self.value
169 )
170 }
171}
172
173impl crate::codegen::format::FormatAsRustCode for CounterIncrement {
174 fn format_as_rust_code(&self, _tabs: usize) -> String {
175 alloc::format!(
176 "CounterIncrement {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
177 self.counter_name.as_str(),
178 self.value
179 )
180 }
181}
182
183impl crate::codegen::format::FormatAsRustCode for StringSet {
184 fn format_as_rust_code(&self, _tabs: usize) -> String {
185 format!("StringSet {{ inner: String::from({:?}) }}", self.inner)
186 }
187}
188
189#[cfg(feature = "parser")]
192pub mod parser {
193 #[allow(clippy::wildcard_imports)] use super::*;
195
196 pub fn parse_content(input: &str) -> Result<Content, ()> {
201 Ok(Content {
202 inner: input.trim().into(),
203 })
204 }
205
206 fn parse_counter_name_value(input: &str, default_value: i32) -> Result<(AzString, i32), ()> {
207 let trimmed = input.trim();
208
209 if trimmed == "none" {
210 return Ok((AzString::from_const_str("none"), 0));
211 }
212
213 let parts: Vec<&str> = trimmed.split_whitespace().collect();
214
215 if parts.is_empty() {
216 return Err(());
217 }
218
219 let counter_name = parts[0].into();
220 let value = if parts.len() > 1 {
221 parts[1].parse::<i32>().map_err(|_| ())?
222 } else {
223 default_value
224 };
225
226 Ok((counter_name, value))
227 }
228
229 pub fn parse_counter_reset(input: &str) -> Result<CounterReset, ()> {
233 let (counter_name, value) = parse_counter_name_value(input, 0)?;
234 Ok(CounterReset::new(counter_name, value))
235 }
236
237 pub fn parse_counter_increment(input: &str) -> Result<CounterIncrement, ()> {
241 let (counter_name, value) = parse_counter_name_value(input, 1)?;
242 Ok(CounterIncrement::new(counter_name, value))
243 }
244
245 pub fn parse_string_set(input: &str) -> Result<StringSet, ()> {
249 Ok(StringSet {
250 inner: input.trim().into(),
251 })
252 }
253}
254
255#[cfg(feature = "parser")]
256pub use parser::*;
257
258#[cfg(all(test, feature = "parser"))]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_simple_content_parser() {
264 assert_eq!(parse_content("'Hello'").unwrap().inner.as_str(), "'Hello'");
265
266 let reset = parse_counter_reset("page 1").unwrap();
268 assert_eq!(reset.counter_name.as_str(), "page");
269 assert_eq!(reset.value, 1);
270
271 let reset = parse_counter_reset("list-item 0").unwrap();
272 assert_eq!(reset.counter_name.as_str(), "list-item");
273 assert_eq!(reset.value, 0);
274
275 let reset = parse_counter_reset("none").unwrap();
276 assert_eq!(reset.counter_name.as_str(), "none");
277
278 let inc = parse_counter_increment("section").unwrap();
280 assert_eq!(inc.counter_name.as_str(), "section");
281 assert_eq!(inc.value, 1); let inc = parse_counter_increment("list-item 2").unwrap();
284 assert_eq!(inc.counter_name.as_str(), "list-item");
285 assert_eq!(inc.value, 2);
286
287 assert_eq!(
288 parse_string_set("chapter-title content()")
289 .unwrap()
290 .inner
291 .as_str(),
292 "chapter-title content()"
293 );
294 }
295}