confignode/
confignode.rs

1use std::{collections::HashMap, iter::Peekable, str::Chars};
2
3use crate::error::{ParseError, ParseErrorKind};
4
5/// Represents the value of a ConfigNode key
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub enum ConfigNodeValue {
8    /// Any value that is not a new node. Numbers, lists, and other types of values are not parsed and are represented as their original textual form as this type.
9    Text(String),
10    /// A new node
11    Node(ConfigNode),
12}
13
14/// Represents a node in the ConfigNode tree
15#[derive(Clone, Debug, Eq, PartialEq, Default)]
16pub struct ConfigNode {
17    /// All the direct children of this node
18    pub children: HashMap<String, ConfigNodeValue>,
19}
20
21/// Parses a string into a [`ConfigNode`] struct
22pub struct ConfigNodeParser<'a> {
23    it: Peekable<Chars<'a>>,
24}
25
26impl ConfigNodeParser<'_> {
27    /// Parses a ConfigNode string into a [`ConfigNode`] struct.
28    pub fn parse(text: &str) -> Result<ConfigNode, ParseError> {
29        let mut parser = ConfigNodeParser {
30            it: text.chars().peekable(),
31        };
32
33        parser.parse_confignode()
34    }
35
36    // parses a node *without* the surrounding curly brackets
37    fn parse_confignode(&mut self) -> Result<ConfigNode, ParseError> {
38        let mut children = HashMap::new();
39
40        self.skip_whitespace();
41
42        loop {
43            let identifier = match self.it.peek() {
44                Some('}') | None => {
45                    break;
46                }
47                Some('/') => {
48                    self.it.next();
49
50                    if self.it.peek() == Some(&'/') {
51                        self.skip_line();
52                        self.skip_whitespace();
53                        continue;
54                    } else {
55                        let mut identifier = self.parse_identifier()?;
56                        identifier.insert(0, '/');
57                        identifier
58                    }
59                }
60                Some(_) => self.parse_identifier()?,
61            };
62
63            self.skip_whitespace();
64
65            match self.it.peek() {
66                Some('{') => {
67                    self.it.next();
68                    children.insert(identifier, ConfigNodeValue::Node(self.parse_confignode()?));
69                    self.skip_whitespace();
70
71                    match self.it.next() {
72                        Some('}') => {}
73                        Some(_) => {
74                            // i think this should be unreachable
75                            return Err(ParseError {
76                                kind: ParseErrorKind::InvalidCharacter,
77                            });
78                        }
79                        None => {
80                            return Err(ParseError {
81                                kind: ParseErrorKind::UnexpectedEof,
82                            })
83                        }
84                    }
85                }
86                Some('=') => {
87                    self.it.next();
88                    children.insert(identifier, ConfigNodeValue::Text(self.parse_string()?));
89                }
90                Some(_) => {
91                    // i think this should be unreachable
92                    return Err(ParseError {
93                        kind: ParseErrorKind::InvalidCharacter,
94                    });
95                }
96                None => {
97                    return Err(ParseError {
98                        kind: ParseErrorKind::UnexpectedEof,
99                    })
100                }
101            }
102
103            self.skip_whitespace();
104        }
105
106        Ok(ConfigNode { children })
107    }
108
109    fn parse_identifier(&mut self) -> Result<String, ParseError> {
110        let mut identifier = String::new();
111
112        loop {
113            match self.it.peek() {
114                Some('{') | Some('=') | Some('\n') | Some('\r') => {
115                    break;
116                }
117                Some('/') => {
118                    self.it.next();
119
120                    if self.it.peek() == Some(&'/') {
121                        self.skip_line();
122                        break;
123                    } else {
124                        identifier.push('/');
125                    }
126                }
127                Some('}') => {
128                    return Err(ParseError {
129                        kind: ParseErrorKind::InvalidCharacter,
130                    })
131                }
132                Some(c) => {
133                    identifier.push(*c);
134                    self.it.next();
135                }
136                None => {
137                    return Err(ParseError {
138                        kind: ParseErrorKind::UnexpectedEof,
139                    })
140                }
141            }
142        }
143
144        Ok(identifier.trim().to_owned())
145    }
146
147    fn parse_string(&mut self) -> Result<String, ParseError> {
148        let mut string = String::new();
149
150        while let Some(c) = self.it.peek() {
151            if *c == '\n' || *c == '\r' {
152                break;
153            } else if *c == '/' {
154                self.it.next();
155
156                if self.it.peek() == Some(&'/') {
157                    self.skip_line();
158                    break;
159                } else {
160                    string.push('/');
161                }
162            } else {
163                string.push(*c);
164                self.it.next();
165            }
166        }
167
168        Ok(string.trim().to_owned())
169    }
170
171    fn skip_line(&mut self) {
172        while self.it.peek().map_or(false, |&x| x != '\n') {
173            self.it.next();
174        }
175    }
176
177    fn skip_whitespace(&mut self) {
178        while self.it.peek().map_or(false, |x| x.is_ascii_whitespace()) {
179            self.it.next();
180        }
181    }
182}
183
184impl ConfigNodeValue {
185    /// Returns a reference to the inner tring of the `ConfigNodeValue` enum if `self` is of type [`ConfigNodeValue::Text`],
186    /// otherwise `None`.
187    pub fn as_text(&self) -> Option<&str> {
188        match self {
189            ConfigNodeValue::Text(x) => Some(x),
190            ConfigNodeValue::Node(_) => None,
191        }
192    }
193
194    /// Returns a reference to the inner `ConfigNode` of the `ConfigNodeValue` enum if `self` is of type [`ConfigNodeValue::Node`],
195    /// otherwise `None`.
196    pub fn as_node(&self) -> Option<&ConfigNode> {
197        match self {
198            ConfigNodeValue::Text(_) => None,
199            ConfigNodeValue::Node(x) => Some(x),
200        }
201    }
202}