simpleml/
lib.rs

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
7/// Equivalent to [parse](https://docs.rs/simpleml/latest/simpleml/fn.parse.html),
8/// but returns Strings instead of Cows for better ease of use.
9pub 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
26/// Parses the Simple Markup Language text into a tree of SMLElements.
27/// For details about how to use Tree, see [tree_iterators_rs](https://crates.io/crates/tree_iterators_rs)
28/// and the documentation related to that crate.
29pub 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(), // default to 4 spaces
211            end_keyword: None,              // Use minified as the default
212            column_alignment: ColumnAlignment::default(),
213        }
214    }
215
216    /// Sets the indentation string to be used in the output.
217    /// If the passed in str contains any non-whitespace characters,
218    /// this call will fail and return None.
219    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    /// Sets the end keyword to be used in the output.
228    /// If the passed in string is the empty string "",
229    /// '-' will be used instead.
230    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    /// Sets the column alignment of the attributes' generated WSV.
264    /// The element alignment will be unaffected, but all attributes
265    /// and their values will be aligned this way.
266    pub fn align_columns(mut self, alignment: ColumnAlignment) -> Self {
267        self.column_alignment = alignment;
268        return self;
269    }
270
271    /// Writes the values in this SMLWriter out to a String. This operation
272    /// can fail if any of the values would result in an SML attribute or
273    /// element where the name is the same as the "End" keyword. If that
274    /// happens, you as the caller will receive an Err() variant of Result.
275    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    /// This error can happen for 2 reasons:
480    /// 1. the file was empty or only contained comments
481    /// 2. the file is invalid SML.
482    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        // Build up our value set
796        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        // actually write the values
852        let str = SMLWriter::new(my_sml_values)
853            // Setting up a custom end keyword
854            .with_end_keyword(Some("my_custom_end_keyword"))
855            // Using 8 spaces as the indent string. The default is 4 spaces.
856            .indent_with("        ")
857            .unwrap()
858            // Align the WSV tables to the right.
859            .align_columns(whitespacesv::ColumnAlignment::Right)
860            .to_string()
861            .unwrap();
862
863        /// Result:
864        /// Configuration
865        ///         Video
866        ///                  Resolution 1280 720
867        ///                 RefreshRate   60
868        ///                  Fullscreen true
869        ///         my_custom_end_keyword
870        ///         Audio
871        ///                 Volume 100
872        ///                  Music  80
873        ///         my_custom_end_keyword
874        ///         Player
875        ///                 Name "Hero 123"
876        ///         my_custom_end_keyword
877        /// my_custom_end_keyword
878        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}