1#![doc = include_str!("../README.md")]
2
3use std::{borrow::Cow, error::Error, fmt::Display};
4use tree_iterators_rs::prelude::{BorrowedTreeNode, Tree};
5use whitespacesv::{ColumnAlignment, WSVError, WSVWriter};
6
7pub fn parse_owned(source_text: &str) -> Result<Tree<SMLElement<String>>, ParseError> {
10 let borrowed = parse(source_text)?;
11 Ok(to_owned(borrowed))
12}
13
14fn to_owned(tree: Tree<SMLElement<Cow<'_, str>>>) -> Tree<SMLElement<String>> {
15 let mut new_children = Vec::with_capacity(tree.children.len());
16 for child in tree.children {
17 new_children.push(to_owned(child));
18 }
19
20 Tree {
21 value: tree.value.to_owned(),
22 children: new_children,
23 }
24}
25
26pub fn parse(source_text: &str) -> Result<Tree<SMLElement<Cow<'_, str>>>, ParseError> {
30 let wsv_result = whitespacesv::parse(source_text);
31 let wsv = match wsv_result {
32 Err(err) => return Err(ParseError::WSV(err)),
33 Ok(wsv) => wsv,
34 };
35
36 let end_keyword = match wsv.iter().rev().find(|line| !line.is_empty()) {
37 None => {
38 return Err(ParseError::SML(SMLError {
39 err_type: SMLErrorType::EndKeywordNotDetected,
40 line_num: wsv.len(),
41 }))
42 }
43 Some(last_line) => match last_line.get(0).unwrap() {
44 None => None,
45 Some(val) => Some(val.to_lowercase()),
46 },
47 };
48
49 let mut lines_iter = wsv.into_iter().enumerate();
50 let root_element_name;
51 loop {
52 let first_line = lines_iter.next();
53 match first_line {
54 None => panic!("Found an empty file, but this should've returned an SMLError::EndKeywordNotDetected"),
55 Some((line_num, mut first_line)) => {
56 if first_line.is_empty() { continue; }
57 if first_line.len() > 1 { return Err(ParseError::SML(SMLError {
58 err_type: SMLErrorType::InvalidRootElementStart,
59 line_num,
60 })) }
61 match std::mem::take(first_line.get_mut(0).unwrap()) {
62 None => return Err(ParseError::SML(SMLError {
63 err_type: SMLErrorType::NullValueAsElementName,
64 line_num,
65 })),
66 Some(root) => {
67 root_element_name = root;
68 break;
69 }
70 }
71 }
72 }
73 }
74
75 let root = Tree {
76 value: SMLElement {
77 name: root_element_name,
78 attributes: Vec::with_capacity(0),
79 },
80 children: Vec::new(),
81 };
82 let mut nodes_being_built = vec![root];
83 let mut result = None;
84
85 for (line_num, mut line) in lines_iter {
86 if line.is_empty() {
87 continue;
88 }
89 if line.len() == 1 {
90 let val;
91 let val_lowercase;
92 match line.get_mut(0) {
93 None => {
94 if end_keyword.is_some() {
95 return Err(ParseError::SML(SMLError {
96 err_type: SMLErrorType::NullValueAsElementName,
97 line_num,
98 }));
99 }
100 val = None;
101 val_lowercase = None;
102 }
103 Some(inner_val) => match inner_val {
104 None => {
105 val = None;
106 val_lowercase = None;
107 }
108 Some(innermost_val) => {
109 val_lowercase = Some(innermost_val.to_lowercase());
110 val = Some(std::mem::take(innermost_val));
111 }
112 },
113 };
114
115 if val_lowercase == end_keyword {
116 match nodes_being_built.pop() {
117 None => {
118 return Err(ParseError::SML(SMLError {
119 err_type: SMLErrorType::OnlyOneRootElementAllowed,
120 line_num,
121 }))
122 }
123 Some(top) => {
124 let nodes_being_built_len = nodes_being_built.len();
125 if nodes_being_built_len == 0 {
126 if result.is_some() {
127 return Err(ParseError::SML(SMLError {
128 err_type: SMLErrorType::OnlyOneRootElementAllowed,
129 line_num,
130 }));
131 } else {
132 result = Some(top);
133 continue;
134 }
135 }
136
137 let new_top = nodes_being_built
138 .get_mut(nodes_being_built_len - 1)
139 .unwrap();
140
141 new_top.children.push(top);
142 }
143 }
144 } else {
145 nodes_being_built.push(Tree {
146 value: SMLElement {
147 name: val.expect("BUG: Null element names are prohibited."),
148 attributes: Vec::with_capacity(0),
149 },
150 children: Vec::new(),
151 });
152 }
153 } else {
154 let mut values = line.into_iter();
155 let name = match values.next().unwrap() {
156 None => {
157 return Err(ParseError::SML(SMLError {
158 err_type: SMLErrorType::NullValueAsAttributeName,
159 line_num,
160 }))
161 }
162 Some(val) => val,
163 };
164
165 let attr_values = values.collect::<Vec<_>>();
166 let nodes_being_built_len = nodes_being_built.len();
167 if nodes_being_built_len == 0 {
168 return Err(ParseError::SML(SMLError {
169 err_type: SMLErrorType::OnlyOneRootElementAllowed,
170 line_num,
171 }));
172 }
173
174 let current = nodes_being_built
175 .get_mut(nodes_being_built_len - 1)
176 .unwrap();
177 current.value.attributes.push(SMLAttribute {
178 name,
179 values: attr_values,
180 });
181 }
182 }
183
184 match result {
185 None => Err(ParseError::SML(SMLError {
186 err_type: SMLErrorType::RootNotClosed,
187 line_num: 0,
188 })),
189 Some(result) => Ok(result),
190 }
191}
192
193pub struct SMLWriter<'values, StrAsRef>
194where
195 StrAsRef: AsRef<str> + From<&'static str> + ToString,
196{
197 indent_str: String,
198 end_keyword: Option<String>,
199 column_alignment: ColumnAlignment,
200 values: &'values Tree<SMLElement<StrAsRef>>,
201}
202
203impl<'values, StrAsRef> SMLWriter<'values, StrAsRef>
204where
205 StrAsRef: AsRef<str> + From<&'static str> + ToString,
206{
207 pub fn new(values: &'values Tree<SMLElement<StrAsRef>>) -> Self {
208 Self {
209 values,
210 indent_str: " ".to_string(), end_keyword: None, column_alignment: ColumnAlignment::default(),
213 }
214 }
215
216 pub fn indent_with(mut self, str: &str) -> Option<Self> {
220 if str.chars().any(|ch| !Self::is_whitespace(ch)) {
221 return None;
222 }
223 self.indent_str = str.to_string();
224 return Some(self);
225 }
226
227 pub fn with_end_keyword(mut self, str: Option<&str>) -> Self {
231 match str {
232 None | Some("") => {
233 self.end_keyword = None;
234 return self;
235 }
236 Some(str) => {
237 debug_assert!(!str.is_empty());
238 let needs_quotes = str
239 .chars()
240 .any(|ch| ch == '"' || ch == '#' || ch == '\n' || Self::is_whitespace(ch))
241 || str == "-";
242
243 if !needs_quotes {
244 self.end_keyword = Some(str.to_string());
245 } else {
246 let mut result = String::new();
247 result.push('"');
248 for ch in str.chars() {
249 match ch {
250 '"' => result.push_str("\"\""),
251 '\n' => result.push_str("\"/\""),
252 ch => result.push(ch),
253 }
254 }
255 result.push('"');
256 self.end_keyword = Some(result);
257 }
258 return self;
259 }
260 }
261 }
262
263 pub fn align_columns(mut self, alignment: ColumnAlignment) -> Self {
267 self.column_alignment = alignment;
268 return self;
269 }
270
271 pub fn to_string(self) -> Result<String, SMLWriterError> {
276 let mut result = String::new();
277 Self::to_string_helper(
278 &self.values,
279 0,
280 &self.column_alignment,
281 &self.indent_str,
282 self.end_keyword.as_ref(),
283 &mut result,
284 )?;
285 return Ok(result);
286 }
287
288 fn to_string_helper(
289 value: &Tree<SMLElement<StrAsRef>>,
290 depth: usize,
291 alignment: &ColumnAlignment,
292 indent_str: &str,
293 end_keyword: Option<&String>,
294 buf: &mut String,
295 ) -> Result<(), SMLWriterError> {
296 let (value, children) = value.get_value_and_children_iter();
297 if let Some(end_keyword) = end_keyword {
298 if value.name.as_ref() == end_keyword {
299 return Err(SMLWriterError::ElementHasEndKeywordName);
300 }
301 }
302
303 for _ in 0..depth {
304 buf.push_str(indent_str);
305 }
306 buf.push_str(value.name.as_ref());
307
308 if !value.attributes.is_empty() {
309 buf.push('\n');
310 for _ in 0..depth + 1 {
311 buf.push_str(indent_str);
312 }
313 }
314
315 if let Some(end_keyword) = end_keyword {
316 for attribute in value.attributes.iter() {
317 if attribute.name.as_ref() == end_keyword {
318 return Err(SMLWriterError::AttributeHasEndKeywordName);
319 }
320 }
321 }
322
323 let values_for_writer = value.attributes.iter().map(|attr| {
324 std::iter::once(Some(attr.name.as_ref())).chain(attr.values.iter().map(|value| {
325 match value {
326 None => None,
327 Some(val) => Some(val.as_ref()),
328 }
329 }))
330 });
331
332 match alignment {
333 ColumnAlignment::Packed => {
334 for ch in WSVWriter::new(values_for_writer) {
335 buf.push(ch);
336 if ch == '\n' {
337 for _ in 0..depth + 1 {
338 buf.push_str(indent_str);
339 }
340 }
341 }
342 }
343 ColumnAlignment::Left | ColumnAlignment::Right => {
344 for ch in WSVWriter::new(values_for_writer)
345 .align_columns(match alignment {
346 ColumnAlignment::Left => ColumnAlignment::Left,
347 ColumnAlignment::Right => ColumnAlignment::Right,
348 ColumnAlignment::Packed => {
349 panic!("BUG: ColumnAlignment::Packed shouldn't go down this path")
350 }
351 })
352 .to_string()
353 .chars()
354 {
355 buf.push(ch);
356 if ch == '\n' {
357 for _ in 0..depth + 1 {
358 buf.push_str(indent_str);
359 }
360 }
361 }
362 }
363 }
364
365 for child in children.into_iter() {
366 buf.push('\n');
367 Self::to_string_helper(child, depth + 1, alignment, indent_str, end_keyword, buf)?;
368 }
369 buf.push('\n');
370 for _ in 0..depth {
371 buf.push_str(indent_str);
372 }
373 match end_keyword {
374 None => buf.push('-'),
375 Some(end) => buf.push_str(end),
376 }
377
378 return Ok(());
379 }
380
381 const fn is_whitespace(ch: char) -> bool {
382 match ch {
383 '\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{000D}' | '\u{0020}' | '\u{0085}'
384 | '\u{00A0}' | '\u{1680}' | '\u{2000}' | '\u{2001}' | '\u{2002}' | '\u{2003}'
385 | '\u{2004}' | '\u{2005}' | '\u{2006}' | '\u{2007}' | '\u{2008}' | '\u{2009}'
386 | '\u{200A}' | '\u{2028}' | '\u{2029}' | '\u{202F}' | '\u{205F}' | '\u{3000}' => {
387 return true;
388 }
389 _ => return false,
390 }
391 }
392}
393
394#[derive(Debug, Clone, Copy)]
395pub enum SMLWriterError {
396 ElementHasEndKeywordName,
397 AttributeHasEndKeywordName,
398}
399
400impl Error for SMLWriterError {}
401impl Display for SMLWriterError {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 match self {
404 SMLWriterError::AttributeHasEndKeywordName => {
405 write!(f, "Attribute Has End Keyword Name")?
406 }
407 SMLWriterError::ElementHasEndKeywordName => write!(f, "Element Has End Keyword Name")?,
408 }
409 Ok(())
410 }
411}
412
413#[derive(Debug, Clone)]
414pub enum ParseError {
415 WSV(WSVError),
416 SML(SMLError),
417}
418
419impl Error for ParseError {}
420impl Display for ParseError {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 match self {
423 ParseError::SML(err) => err.fmt(f)?,
424 ParseError::WSV(err) => err.fmt(f)?,
425 }
426 Ok(())
427 }
428}
429
430#[derive(Debug, Clone)]
431pub struct SMLError {
432 err_type: SMLErrorType,
433 line_num: usize,
434}
435
436impl SMLError {
437 pub fn err_type(&self) -> SMLErrorType {
438 self.err_type
439 }
440 pub fn line_num(&self) -> usize {
441 self.line_num
442 }
443}
444
445impl Error for SMLError {}
446impl Display for SMLError {
447 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448 let mut result = String::new();
449 result.push_str("(line: ");
450 result.push_str(&self.line_num().to_string());
451 result.push_str(") ");
452 match self.err_type() {
453 SMLErrorType::EndKeywordNotDetected => {
454 result.push_str("End Keyword Not Detected");
455 }
456 SMLErrorType::InvalidRootElementStart => {
457 result.push_str("Invalid Root Element Start");
458 }
459 SMLErrorType::NullValueAsAttributeName => {
460 result.push_str("Null Value as Attribute Name");
461 }
462 SMLErrorType::NullValueAsElementName => {
463 result.push_str("Null Value as Element Name");
464 }
465 SMLErrorType::OnlyOneRootElementAllowed => {
466 result.push_str("Only One Root Element Allowed");
467 }
468 SMLErrorType::RootNotClosed => {
469 result.push_str("Root Not Closed");
470 }
471 }
472 write!(f, "{}", result)?;
473 Ok(())
474 }
475}
476
477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
478pub enum SMLErrorType {
479 EndKeywordNotDetected,
483 InvalidRootElementStart,
484 NullValueAsElementName,
485 NullValueAsAttributeName,
486 RootNotClosed,
487 OnlyOneRootElementAllowed,
488}
489
490#[derive(Debug)]
491pub struct SMLElement<StrAsRef>
492where
493 StrAsRef: AsRef<str>,
494{
495 pub name: StrAsRef,
496 pub attributes: Vec<SMLAttribute<StrAsRef>>,
497}
498
499impl SMLElement<Cow<'_, str>> {
500 fn to_owned(self) -> SMLElement<String> {
501 let mut attributes = Vec::with_capacity(self.attributes.len());
502 for attr in self.attributes {
503 attributes.push(attr.to_owned());
504 }
505
506 SMLElement {
507 name: match self.name {
508 Cow::Borrowed(str) => str.to_string(),
509 Cow::Owned(string) => string,
510 },
511 attributes,
512 }
513 }
514}
515
516#[derive(Debug)]
517pub struct SMLAttribute<StrAsRef>
518where
519 StrAsRef: AsRef<str>,
520{
521 pub name: StrAsRef,
522 pub values: Vec<Option<StrAsRef>>,
523}
524
525impl SMLAttribute<Cow<'_, str>> {
526 fn to_owned(self) -> SMLAttribute<String> {
527 let mut values = Vec::with_capacity(self.values.len());
528 for value in self.values {
529 let new_value = match value {
530 None => None,
531 Some(cow) => match cow {
532 Cow::Borrowed(str) => Some(str.to_string()),
533 Cow::Owned(string) => Some(string),
534 },
535 };
536
537 values.push(new_value);
538 }
539 SMLAttribute {
540 name: match self.name {
541 Cow::Borrowed(str) => str.to_string(),
542 Cow::Owned(string) => string,
543 },
544 values,
545 }
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use tree_iterators_rs::prelude::OwnedTreeNode;
552
553 use crate::{SMLAttribute, SMLElement, SMLWriter};
554
555 #[test]
556 fn reads_example_correctly() {
557 let result = super::parse(include_str!("../example.txt")).unwrap();
558 for (i, element) in result.dfs_preorder().enumerate() {
559 match i {
560 0 => {
561 assert_eq!("Configuration", &element.name);
562 assert_eq!(0, element.attributes.len());
563 }
564 1 => {
565 assert_eq!("Video", &element.name);
566 assert_eq!(3, element.attributes.len());
567 for (j, attribute) in element.attributes.into_iter().enumerate() {
568 match j {
569 0 => {
570 assert_eq!("Resolution", attribute.name);
571 assert_eq!(2, attribute.values.len());
572 assert_eq!(
573 "1280",
574 attribute
575 .values
576 .get(0)
577 .as_ref()
578 .unwrap()
579 .as_ref()
580 .unwrap()
581 .as_ref()
582 );
583 assert_eq!(
584 "720",
585 attribute
586 .values
587 .get(1)
588 .as_ref()
589 .unwrap()
590 .as_ref()
591 .unwrap()
592 .as_ref()
593 );
594 }
595 1 => {
596 assert_eq!("RefreshRate", attribute.name);
597 assert_eq!(1, attribute.values.len());
598 assert_eq!(
599 "60",
600 attribute
601 .values
602 .get(0)
603 .as_ref()
604 .unwrap()
605 .as_ref()
606 .unwrap()
607 .as_ref()
608 );
609 }
610 2 => {
611 assert_eq!("Fullscreen", attribute.name);
612 assert_eq!(1, attribute.values.len());
613 assert_eq!(
614 "true",
615 attribute
616 .values
617 .get(0)
618 .as_ref()
619 .unwrap()
620 .as_ref()
621 .unwrap()
622 .as_ref()
623 );
624 }
625 _ => panic!("Should only have 3 attributes"),
626 }
627 }
628 }
629 2 => {
630 assert_eq!("Audio", &element.name);
631 assert_eq!(2, element.attributes.len());
632 for (j, attribute) in element.attributes.into_iter().enumerate() {
633 match j {
634 0 => {
635 assert_eq!("Volume", attribute.name);
636 assert_eq!(1, attribute.values.len());
637 assert_eq!(
638 "100",
639 attribute
640 .values
641 .get(0)
642 .as_ref()
643 .unwrap()
644 .as_ref()
645 .unwrap()
646 .as_ref()
647 );
648 }
649 1 => {
650 assert_eq!("Music", attribute.name);
651 assert_eq!(1, attribute.values.len());
652 assert_eq!(
653 "80",
654 attribute
655 .values
656 .get(0)
657 .as_ref()
658 .unwrap()
659 .as_ref()
660 .unwrap()
661 .as_ref()
662 );
663 }
664 _ => panic!("Should only have 2 values under audio"),
665 }
666 }
667 }
668 3 => {
669 assert_eq!("Player", element.name);
670 assert_eq!(1, element.attributes.len());
671 let attr = element.attributes.get(0).unwrap();
672 assert_eq!("Name", attr.name);
673 assert_eq!(1, attr.values.len());
674 assert_eq!(
675 "Hero 123",
676 attr.values
677 .get(0)
678 .as_ref()
679 .unwrap()
680 .as_ref()
681 .unwrap()
682 .as_ref()
683 );
684 }
685 _ => panic!("Should only have 4 sub-elements"),
686 }
687 }
688 }
689
690 #[test]
691 fn reads_example_correctly_owned() {
692 let result = super::parse_owned(include_str!("../example.txt")).unwrap();
693 for (i, element) in result.dfs_preorder().enumerate() {
694 match i {
695 0 => {
696 assert_eq!("Configuration", &element.name);
697 assert_eq!(0, element.attributes.len());
698 }
699 1 => {
700 assert_eq!("Video", &element.name);
701 assert_eq!(3, element.attributes.len());
702 for (j, attribute) in element.attributes.into_iter().enumerate() {
703 match j {
704 0 => {
705 assert_eq!("Resolution", attribute.name);
706 assert_eq!(2, attribute.values.len());
707 assert_eq!(
708 "1280",
709 attribute.values.get(0).as_ref().unwrap().as_ref().unwrap()
710 );
711 assert_eq!(
712 "720",
713 attribute.values.get(1).as_ref().unwrap().as_ref().unwrap()
714 );
715 }
716 1 => {
717 assert_eq!("RefreshRate", attribute.name);
718 assert_eq!(1, attribute.values.len());
719 assert_eq!(
720 "60",
721 attribute.values.get(0).as_ref().unwrap().as_ref().unwrap()
722 );
723 }
724 2 => {
725 assert_eq!("Fullscreen", attribute.name);
726 assert_eq!(1, attribute.values.len());
727 assert_eq!(
728 "true",
729 attribute.values.get(0).as_ref().unwrap().as_ref().unwrap()
730 );
731 }
732 _ => panic!("Should only have 3 attributes"),
733 }
734 }
735 }
736 2 => {
737 assert_eq!("Audio", &element.name);
738 assert_eq!(2, element.attributes.len());
739 for (j, attribute) in element.attributes.into_iter().enumerate() {
740 match j {
741 0 => {
742 assert_eq!("Volume", attribute.name);
743 assert_eq!(1, attribute.values.len());
744 assert_eq!(
745 "100",
746 attribute.values.get(0).as_ref().unwrap().as_ref().unwrap()
747 );
748 }
749 1 => {
750 assert_eq!("Music", attribute.name);
751 assert_eq!(1, attribute.values.len());
752 assert_eq!(
753 "80",
754 attribute.values.get(0).as_ref().unwrap().as_ref().unwrap()
755 );
756 }
757 _ => panic!("Should only have 2 values under audio"),
758 }
759 }
760 }
761 3 => {
762 assert_eq!("Player", element.name);
763 assert_eq!(1, element.attributes.len());
764 let attr = element.attributes.get(0).unwrap();
765 assert_eq!("Name", attr.name);
766 assert_eq!(1, attr.values.len());
767 assert_eq!(
768 "Hero 123",
769 attr.values.get(0).as_ref().unwrap().as_ref().unwrap()
770 );
771 }
772 _ => panic!("Should only have 4 sub-elements"),
773 }
774 }
775 }
776
777 #[test]
778 fn test_write() {
779 let input = include_str!("../example.txt");
780 println!(
781 "{}",
782 super::SMLWriter::new(super::parse(input).unwrap())
783 .align_columns(whitespacesv::ColumnAlignment::Right)
784 .indent_with(" ")
785 .unwrap()
786 .to_string()
787 .unwrap()
788 );
789 }
790
791 #[test]
792 fn readme_example() {
793 use tree_iterators_rs::prelude::*;
794
795 let my_sml_values = Tree {
797 value: SMLElement {
798 name: "Configuration",
799 attributes: Vec::with_capacity(0),
800 },
801 children: vec![
802 Tree {
803 value: SMLElement {
804 name: "Video",
805 attributes: vec![
806 SMLAttribute {
807 name: "Resolution",
808 values: vec![Some("1280"), Some("720")],
809 },
810 SMLAttribute {
811 name: "RefreshRate",
812 values: vec![Some("60")],
813 },
814 SMLAttribute {
815 name: "Fullscreen",
816 values: vec![Some("true")],
817 },
818 ],
819 },
820 children: Vec::new(),
821 },
822 Tree {
823 value: SMLElement {
824 name: "Audio",
825 attributes: vec![
826 SMLAttribute {
827 name: "Volume",
828 values: vec![Some("100")],
829 },
830 SMLAttribute {
831 name: "Music",
832 values: vec![Some("80")],
833 },
834 ],
835 },
836 children: Vec::new(),
837 },
838 Tree {
839 value: SMLElement {
840 name: "Player",
841 attributes: vec![SMLAttribute {
842 name: "Name",
843 values: vec![Some("Hero 123")],
844 }],
845 },
846 children: Vec::new(),
847 },
848 ],
849 };
850
851 let str = SMLWriter::new(my_sml_values)
853 .with_end_keyword(Some("my_custom_end_keyword"))
855 .indent_with(" ")
857 .unwrap()
858 .align_columns(whitespacesv::ColumnAlignment::Right)
860 .to_string()
861 .unwrap();
862
863 println!("{}", str);
879 }
880
881 #[test]
882 fn parses_input_with_strange_end_keyword() {
883 let input = r#"
884 Configuration
885 Video
886 Resolution 1280 720
887 RefreshRate 60
888 Fullscreen true
889 "End with spaces"
890 Audio
891 Volume 100
892 Music 80
893 "End with spaces"
894 Player
895 Name "Hero 123"
896 "End with spaces"
897 "End with spaces""#;
898
899 println!("{:?}", super::parse(input).unwrap());
900 }
901
902 #[test]
903 fn parses_input_with_strange_end_keyword_owned() {
904 let input = r#"
905 Configuration
906 Video
907 Resolution 1280 720
908 RefreshRate 60
909 Fullscreen true
910 "End with spaces"
911 Audio
912 Volume 100
913 Music 80
914 "End with spaces"
915 Player
916 Name "Hero 123"
917 "End with spaces"
918 "End with spaces""#;
919
920 println!("{:?}", super::parse_owned(input).unwrap());
921 }
922
923 #[test]
924 fn parses_input_with_null_end_keyword() {
925 let input = r#"
926 Configuration
927 Video
928 Resolution 1280 720
929 RefreshRate 60
930 Fullscreen true
931 -
932 Audio
933 Volume 100
934 Music 80
935 -
936 Player
937 Name "Hero 123"
938 -
939 -"#;
940
941 println!("{:?}", super::parse(input).unwrap());
942 }
943
944 #[test]
945 fn doesnt_crash_bad_input() {
946 let str = r#"Value1
947 Configuration 2 0 2
948 Otherval 1
949 InnerEl
950 -"#;
951
952 super::parse(str).ok();
953 }
954}