1use std::collections::HashSet;
2use State::*;
3
4use crate::{
5 builder::{generate, LayoutElement},
6 media_query::{extract_breakpoint, MediaQuery},
7};
8
9#[derive(Debug, PartialEq)]
10pub enum State {
11 Resting,
12 InsideTag,
13 ReadingTagName,
14 AfterTagName,
15 ReadingAttributeName,
16 WaitingAttributeValue,
17 ReadingAttributeValue,
18}
19
20pub struct Parser<'a> {
21 pub state: State,
22 pub text: &'a str,
23 pub tag_name_start: Option<usize>,
24 pub tag_name_end: Option<usize>,
25 pub attribute_name_start: Option<usize>,
26 pub attribute_name_end: Option<usize>,
27 pub layout_attribute_value_start: Option<usize>,
28 pub layout_attribute_value_end: Option<usize>,
29 pub layout_breakpoint_attribute_value_start: Option<usize>,
30 pub layout_breakpoint_attribute_value_end: Option<usize>,
31 pub biggest_breakpoint: Option<usize>,
32 pub biggest_breakpoint_value: Option<&'a str>,
33}
34
35impl<'a> Parser<'a> {
36 pub fn new(text: &'a str) -> Self {
37 Parser {
38 state: Resting,
39 text,
40 tag_name_start: None,
41 tag_name_end: None,
42 attribute_name_start: None,
43 attribute_name_end: None,
44 layout_attribute_value_start: None,
45 layout_attribute_value_end: None,
46 layout_breakpoint_attribute_value_start: None,
47 layout_breakpoint_attribute_value_end: None,
48 biggest_breakpoint: None,
49 biggest_breakpoint_value: None,
50 }
51 }
52
53 pub fn reset_indexes(&mut self) {
54 self.tag_name_start = None;
55 self.tag_name_end = None;
56 self.attribute_name_start = None;
57 self.attribute_name_end = None;
58 self.layout_attribute_value_start = None;
59 self.layout_attribute_value_end = None;
60 self.layout_breakpoint_attribute_value_start = None;
61 self.layout_breakpoint_attribute_value_end = None;
62 self.biggest_breakpoint = None;
63 self.biggest_breakpoint_value = None;
64 }
65
66 pub fn tag_name(&self) -> Option<&'a str> {
67 match (self.tag_name_start, self.tag_name_end) {
68 (Some(start), Some(end)) => Some(&self.text[start..=end]),
69 _ => None,
70 }
71 }
72
73 pub fn tag_name_new(&self) -> &'a str {
74 &self.text[self.tag_name_start.unwrap()..=self.tag_name_end.unwrap()]
75 }
76
77 pub fn attribute_name(&self) -> Option<&'a str> {
78 match (self.attribute_name_start, self.attribute_name_end) {
79 (Some(start), Some(end)) => Some(&self.text[start..=end]),
80 _ => None,
81 }
82 }
83
84 pub fn layout_attribute_value(&self) -> Option<&'a str> {
85 match (
86 self.layout_attribute_value_start,
87 self.layout_attribute_value_end,
88 ) {
89 (Some(start), Some(end)) if end > start => Some(&self.text[start..=end]),
92 _ => None,
93 }
94 }
95
96 pub fn layout_breakpoint_attribute_value(&self) -> Option<&'a str> {
97 match (
98 self.layout_breakpoint_attribute_value_start,
99 self.layout_breakpoint_attribute_value_end,
100 ) {
101
102 (Some(start), Some(end)) if end > start => Some(&self.text[start..=end]),
105 (Some(start), Some(end)) if end <= start => Some(""),
107 _ => None,
108 }
109 }
110
111 pub fn update_biggest_breakpoint(&mut self, breakpoint: usize) -> bool {
114 if let Some(parser_biggest_breakpoint) = self.biggest_breakpoint {
115 if breakpoint > parser_biggest_breakpoint {
116 self.biggest_breakpoint = Some(breakpoint);
117 return true;
118 }
119 } else {
120 self.biggest_breakpoint = Some(breakpoint);
121 return true;
122 }
123 false
124 }
125
126 pub fn transition(&self, c: char) -> Option<State> {
129 match (&self.state, c) {
130 (Resting, '<') => Some(InsideTag),
131 (InsideTag, c) if c.is_alphabetic() => Some(ReadingTagName),
132 (ReadingTagName, c) if c.is_whitespace() => Some(AfterTagName),
133 (AfterTagName, c) if c.is_alphabetic() => Some(ReadingAttributeName),
134 (ReadingAttributeName, c) if c.is_whitespace() => Some(AfterTagName),
135 (ReadingAttributeName, '=') => Some(WaitingAttributeValue),
136 (WaitingAttributeValue, '"') => Some(ReadingAttributeValue),
138 (WaitingAttributeValue, c) if c !='"' => Some(AfterTagName),
139 (ReadingAttributeValue, '"') => Some(AfterTagName),
140 (AfterTagName | ReadingTagName | ReadingAttributeName, '>') => Some(Resting),
141 _ => None,
142 }
143 }
144
145 pub fn parse(&mut self, elements: &mut HashSet<LayoutElement<'a>>) {
148 for (i, c) in self.text.char_indices() {
149 let new_state = self.transition(c);
150 if let Some(state) = new_state {
152 match (&self.state, &state) {
153 (_, ReadingTagName) => self.tag_name_start = Some(i),
154 (ReadingTagName, AfterTagName) => self.tag_name_end = Some(i - 1),
155 (_, ReadingAttributeName) => self.attribute_name_start = Some(i),
156 (ReadingAttributeName, AfterTagName | WaitingAttributeValue) => {
157 self.attribute_name_end = Some(i - 1)
158 }
159 (_, ReadingAttributeValue) => {
160 if let Some(attribute_name) = self.attribute_name() {
161 if attribute_name == "layout" {
162 self.layout_attribute_value_start = Some(i + 1);
163 } else if attribute_name.starts_with("layout") {
164 self.layout_breakpoint_attribute_value_start = Some(i + 1);
165 }
166 }
167 }
168 (ReadingAttributeValue, AfterTagName) => {
169 if let Some(attribute_name) = self.attribute_name() {
170 if attribute_name == "layout" {
171 self.layout_attribute_value_end = Some(i - 1);
172 } else if attribute_name.starts_with("layout") {
176 self.layout_breakpoint_attribute_value_end = Some(i - 1);
177 if let (Some(breakpoint), Some(attribute_value)) = (
178 extract_breakpoint(attribute_name),
179 self.layout_breakpoint_attribute_value(),
180 ) {
181 let new_biggest_breakpoint_found =
182 self.update_biggest_breakpoint(breakpoint);
183 if new_biggest_breakpoint_found {
184 self.biggest_breakpoint_value =
185 self.layout_breakpoint_attribute_value();
186 }
187 let mq_new = MediaQuery::InferiorOrEqualTo(breakpoint);
189 generate(
190 self.tag_name_new(),
191 Some(attribute_value),
192 Some(mq_new),
193 elements,
194 );
195 }
196 }
197 }
198 }
199
200 (current_state, Resting) => {
203 if current_state == &ReadingTagName {
207 self.tag_name_end = Some(i - 1);
208 }
209 if let (Some(tag_name), layout_value) =
210 (self.tag_name(), self.layout_attribute_value())
211 {
212 let mq = if let (Some(biggest_breakpoint), Some(breakpoint_value)) = (
213 self.biggest_breakpoint,
214 self.biggest_breakpoint_value,
215 ) {
216 Some(MediaQuery::SuperiorTo(biggest_breakpoint, breakpoint_value.to_string()))
217 } else {
218 None
219 };
220 generate(tag_name, layout_value, mq, elements);
221 }
222 self.reset_indexes();
223 }
224 _ => {}
225 };
226 self.state = state;
228 }
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use crate::builder::LayoutElement;
236
237 use super::*;
238
239 #[test]
241 fn curly_braces_as_attribute_delimiters_working() {
242 let mut set: HashSet<LayoutElement> = HashSet::new();
243 let mut parser =
244 Parser::new("<div class={hello} layout=\"p:3\"");
245 parser.parse(&mut set);
246 assert_eq!(parser.layout_attribute_value_start, Some(27));
247 }
248
249 #[test]
251 fn media_query_update_biggest_breakpoint_value_of_parser_when_many_breakpoints() {
252 let mut set: HashSet<LayoutElement> = HashSet::new();
253 let mut parser =
254 Parser::new("<div layout600px=\"p:3\" layout900px=\"p:7\" layout700px=\"p:1\"");
255 parser.parse(&mut set);
256 assert_eq!(parser.biggest_breakpoint_value, Some("p:7"));
257 }
258 #[test]
259 fn media_query_update_biggest_breakpoint_value_of_parser() {
260 let mut set: HashSet<LayoutElement> = HashSet::new();
261 let mut parser = Parser::new("<div layout600px=\"p:3\"");
262 parser.parse(&mut set);
263 assert_eq!(parser.biggest_breakpoint_value, Some("p:3"));
264 }
265
266 #[test]
267 fn media_query_update_biggest_breakpoint_of_parser() {
268 let mut set: HashSet<LayoutElement> = HashSet::new();
269 let mut parser = Parser::new("<div layout600px=\"p:3\"");
270 parser.parse(&mut set);
271 println!("{:?}", set);
272 assert_eq!(parser.biggest_breakpoint, Some(600));
273 }
274 #[test]
275 fn media_query_only_become_layout_element() {
276 let mut set: HashSet<LayoutElement> = HashSet::new();
277 let mut parser = Parser::new("<div layout600px=\"p:3\"");
278 parser.parse(&mut set);
279 println!("{:?}", set);
280 }
281 #[test]
282 fn layout_bp_attribute_value_start_and_end_are_correct() {
283 let mut set: HashSet<LayoutElement> = HashSet::new();
284 let mut parser = Parser::new("<row-l layout=\"gap:1\" layout600px=\"gap:2 p:3\"");
285 parser.parse(&mut set);
286 assert_eq!(parser.attribute_name(), Some("layout600px"));
287 assert_eq!(parser.layout_breakpoint_attribute_value_start, Some(35));
288 assert_eq!(parser.layout_breakpoint_attribute_value_end, Some(43));
289 }
290
291 #[test]
292 fn component_without_layout_attribute_generate_css() {
293 let mut set: HashSet<LayoutElement> = HashSet::new();
294 let mut parser = Parser::new("<row-l>");
295 parser.parse(&mut set);
296 assert!(set.len() == 1);
297 }
298
299 #[test]
300 fn test_extract_breakpoint_attribute_is_added_to_breakpoints() {
301 let mut set: HashSet<LayoutElement> = HashSet::new();
302 let mut parser = Parser::new("<row-l layout600px=\"p:2\"");
303 parser.parse(&mut set);
304 assert_eq!(parser.layout_breakpoint_attribute_value_start, Some(20));
305 }
306
307 #[test]
308 fn test_extract_breakpoint_with_nothing_after_at() {
309 let mq_attribute_value = "layout";
310 let result = extract_breakpoint(mq_attribute_value);
311
312 assert_eq!(result, None);
313 }
314
315
316
317 #[test]
318 fn test_extract_breakpoint_with_correct_formating() {
319 let mq_attribute_value = "layout600px";
320 let result = extract_breakpoint(mq_attribute_value);
321 assert_eq!(result, Some(600));
322 }
323 #[test]
326 fn when_state_change_for_resting_the_parser_indexes_have_to_be_reset() {
327 let mut set: HashSet<LayoutElement> = HashSet::new();
328 let mut parser = Parser::new("<div layout=\"gap:2\" >");
329 parser.parse(&mut set);
330 assert_eq!(parser.tag_name_start, None);
331 assert_eq!(parser.tag_name_end, None);
332 assert_eq!(parser.attribute_name_start, None);
333 assert_eq!(parser.attribute_name_end, None);
334 assert_eq!(parser.layout_attribute_value_start, None);
335 assert_eq!(parser.layout_attribute_value_end, None);
336 }
337
338 #[test]
339 fn other_attribute_name_than_layout_doesnt_set_attribute_value_start_and_attribute_value_end() {
340 let mut set: HashSet<LayoutElement> = HashSet::new();
341 let mut parser = Parser::new("<div class=\"bonsoir\"");
342 parser.parse(&mut set);
343 assert_eq!(parser.layout_attribute_value_start, None);
344 assert_eq!(parser.layout_attribute_value_end, None);
345 }
346
347 #[test]
348 fn state_changing_from_reading_attribute_value_to_after_tag_name_set_attribute_value_end() {
349 let mut set: HashSet<LayoutElement> = HashSet::new();
350 let mut parser = Parser::new("<div layout=\"bonsoir\"");
351 parser.parse(&mut set);
352 assert_eq!(parser.layout_attribute_value_end, Some(19));
353 }
354
355 #[test]
356 fn check_attribute_value_start_and_end_when_empty_attribute_value() {
357 let mut set: HashSet<LayoutElement> = HashSet::new();
358 let mut parser = Parser::new("<div layout=\"\" ");
359 parser.parse(&mut set);
360 assert_eq!(parser.layout_attribute_value(), None);
361 }
362
363 #[test]
364 fn state_changing_to_reading_attribute_value_set_attribute_value_start() {
365 let mut set: HashSet<LayoutElement> = HashSet::new();
366 let mut parser = Parser::new("<div layout=\" ");
367 parser.parse(&mut set);
368 assert_eq!(parser.layout_attribute_value_start, Some(13));
369 }
370
371 #[test]
372 fn state_changing_from_reading_attribute_name_to_after_tag_name_or_waiting_attribute_value_set_attribute_name_end(
373 ) {
374 let mut set: HashSet<LayoutElement> = HashSet::new();
375 let mut parser = Parser::new("<div class ");
376 parser.parse(&mut set);
377 assert_eq!(parser.attribute_name_end, Some(9));
378 }
379
380 #[test]
381 fn state_to_reading_attribute_name_set_attribute_name_start() {
382 let mut set: HashSet<LayoutElement> = HashSet::new();
383 let mut parser = Parser::new("<div c");
384 parser.parse(&mut set);
385 assert_eq!(parser.attribute_name_start, Some(5));
386 }
387
388 #[test]
389 fn state_changing_from_reading_tag_name_to_after_tag_name_set_tag_name_end() {
390 let mut set: HashSet<LayoutElement> = HashSet::new();
391 let mut parser = Parser::new("<div ");
392 parser.parse(&mut set);
393 assert_eq!(parser.tag_name_end, Some(3));
394 }
395
396 #[test]
397 fn state_changing_to_reading_tag_name_set_tag_name_start() {
398 let mut set: HashSet<LayoutElement> = HashSet::new();
399 let mut parser = Parser::new("<d");
400 parser.parse(&mut set);
401 assert_eq!(parser.tag_name_start, Some(1));
402 }
403 #[test]
406 fn reading_attribute_value_and_anything_except_double_quote_return_same_state() {
407 let mut parser = Parser::new("");
408 parser.state = ReadingAttributeValue;
409 assert_eq!(parser.transition('i'), None);
410 }
411
412 #[test]
413 fn reading_tag_name_or_attribute_name_and_alphabetic_return_same_state() {
414 let mut parser = Parser::new("");
415
416 parser.state = ReadingTagName;
417 assert_eq!(parser.transition('i'), None);
418
419 parser.state = ReadingAttributeName;
420 assert_eq!(parser.transition('i'), None);
421 }
422
423 #[test]
424 fn reading_attribute_value_and_double_quote_return_after_tag_name() {
425 let mut parser = Parser::new("");
426 parser.state = ReadingAttributeValue;
427 assert_eq!(parser.transition('"'), Some(AfterTagName));
428 }
429
430 #[test]
431 fn waiting_attribute_value_and_double_quote_return_reading_attribute_value() {
432 let mut parser = Parser::new("");
433 parser.state = WaitingAttributeValue;
434 assert_eq!(parser.transition('"'), Some(ReadingAttributeValue));
435 }
436
437 #[test]
438 fn reading_attribute_name_and_equal_return_waiting_attribute_value() {
439 let mut parser = Parser::new("");
440 parser.state = ReadingAttributeName;
441 assert_eq!(parser.transition('='), Some(WaitingAttributeValue));
442 }
443
444 #[test]
445 fn reading_attribute_name_and_right_chevron_return_resting() {
446 let mut parser = Parser::new("");
447 parser.state = ReadingAttributeName;
448 assert_eq!(parser.transition('>'), Some(Resting));
449 }
450
451 #[test]
452 fn reading_tag_name_and_right_chevron_return_resting() {
453 let mut parser = Parser::new("");
454 parser.state = ReadingTagName;
455 assert_eq!(parser.transition('>'), Some(Resting));
456 }
457
458 #[test]
459 fn resting_and_left_chevron_return_inside_tag() {
460 let parser = Parser::new("");
461 assert_eq!(parser.transition('<'), Some(InsideTag));
462 }
463
464 #[test]
465 fn resting_and_a_return_resting() {
466 let parser = Parser::new("");
467 assert_eq!(parser.transition('a'), None);
468 }
469
470 #[test]
471 fn inside_tag_and_alpha_return_reading_tag_name() {
472 let mut parser = Parser::new("");
473 parser.state = InsideTag;
474 assert_eq!(parser.transition('d'), Some(ReadingTagName));
475 }
476
477 #[test]
478 fn reading_tag_name_and_whitespace_return_after_tag_name() {
479 let mut parser = Parser::new("");
480 parser.state = ReadingTagName;
481 assert_eq!(parser.transition(' '), Some(AfterTagName));
482
483 let mut parser = Parser::new("");
484 parser.state = ReadingTagName;
485 assert_eq!(parser.transition('\n'), Some(AfterTagName));
486
487 let mut parser = Parser::new("");
488 parser.state = ReadingTagName;
489 assert_eq!(parser.transition('\t'), Some(AfterTagName));
490 }
491
492 #[test]
493 fn after_tag_name_and_alphabetic_return_reading_attribute_name() {
494 let mut parser = Parser::new("");
495 parser.state = AfterTagName;
496 assert_eq!(parser.transition('c'), Some(ReadingAttributeName));
497 }
498
499 #[test]
500 fn reading_attribute_name_and_whitespace_return_after_tag_name() {
501 let mut parser = Parser::new("");
502 parser.state = ReadingAttributeName;
503 assert_eq!(parser.transition(' '), Some(AfterTagName));
504 }
505
506 #[test]
507 fn after_tag_name_and_right_chevron_return_resting() {
508 let mut parser = Parser::new("");
509 parser.state = AfterTagName;
510 assert_eq!(parser.transition('>'), Some(Resting));
511 }
512}