Skip to main content

mnk_vmf/types/
editor.rs

1use chumsky::{IterParser, Parser as ChumskyParser};
2
3use crate::{
4    impl_block_properties_parser,
5    parser::{
6        close_block, key_value, key_value_boolean, key_value_numeric, open_block, InternalParser,
7        TokenError, TokenSource,
8    },
9    types::Color,
10    Parser,
11};
12
13/// Represents editor-specific data for entities and brushes
14#[derive(Default, Debug, Clone)]
15pub struct EditorData<'src> {
16    pub color: Color,
17    pub visgroupshown: bool,
18    pub visgroupautoshown: bool,
19    pub groupid: Option<u32>,
20    pub comments: Option<&'src str>,
21    pub logicalpos: Option<&'src str>,
22}
23
24/// Internal [`EditorData`] Properties to be used in a parser impl
25#[derive(Debug, Clone)]
26enum EditorDataProperty<'src> {
27    Color(Color),
28    VisGroupShown(bool),
29    VisGroupAutoShown(bool),
30    GroupId(u32),
31    Comments(&'src str),
32    LogicalPos(&'src str),
33}
34
35/// Public parser trait implementation that allows [`EditorData`] to use ::parse(input) call.
36impl<'src> Parser<'src> for EditorData<'src> {}
37
38/// A [`InternalParser`] implementation for [`EditorData`].
39///
40/// usage: `let editor = EditorData::parser().parse(input);`.
41///
42/// The format that is being parsed here is:
43/// ```ignore
44/// editor
45/// {
46///     "color" "0 111 152"
47///     "visgroupshown" "1"
48///     "visgroupautoshown" "1"
49///     "logicalpos" "[0 10000]"
50///     "comments" "This is a comment"
51/// }
52/// ```
53impl<'src> InternalParser<'src> for EditorData<'src> {
54    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
55    where
56        I: TokenSource<'src>,
57    {
58        impl_block_properties_parser! {
59            property_list: EditorDataProperty = {
60                p_color                = Color::parser()                       => EditorDataProperty::Color,
61                p_visgroupshown        = key_value_boolean("visgroupshown")    => EditorDataProperty::VisGroupShown,
62                p_visgroupautoshown    = key_value_boolean("visgroupautoshown") => EditorDataProperty::VisGroupAutoShown,
63                p_groupid              = key_value_numeric("groupid")          => EditorDataProperty::GroupId,
64                p_comments             = key_value("comments")                 => |s: &str| EditorDataProperty::Comments(s),
65                p_logicalpos           = key_value("logicalpos")               => |s: &str| EditorDataProperty::LogicalPos(s),
66            }
67        }
68
69        open_block("editor")
70            .ignore_then(
71                property_list
72                    .repeated()
73                    .collect::<Vec<EditorDataProperty>>(),
74            )
75            .then_ignore(close_block())
76            .map(|properties: Vec<EditorDataProperty>| {
77                let mut editor = EditorData::default();
78                for prop in properties {
79                    match prop {
80                        EditorDataProperty::Color(val) => editor.color = val,
81                        EditorDataProperty::VisGroupShown(val) => editor.visgroupshown = val,
82                        EditorDataProperty::VisGroupAutoShown(val) => {
83                            editor.visgroupautoshown = val
84                        }
85                        EditorDataProperty::GroupId(val) => editor.groupid = Some(val),
86                        EditorDataProperty::Comments(val) => editor.comments = Some(val),
87                        EditorDataProperty::LogicalPos(val) => editor.logicalpos = Some(val),
88                    }
89                }
90                editor
91            })
92            .boxed()
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::util::lex;
100
101    #[test]
102    fn test_editor_complete_valid() {
103        let input = r#"
104        editor
105        {
106            "color" "0 111 152"
107            "visgroupshown" "1"
108            "visgroupautoshown" "1"
109            "logicalpos" "[0 10000]"
110            "comments" "Test comment"
111        }
112        "#;
113
114        let stream = lex(input);
115        let result = EditorData::parse(stream);
116        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
117
118        let editor = result.unwrap();
119        assert_eq!(editor.color.r, 0);
120        assert_eq!(editor.color.g, 111);
121        assert_eq!(editor.color.b, 152);
122        assert_eq!(editor.visgroupshown, true);
123        assert_eq!(editor.visgroupautoshown, true);
124        assert_eq!(editor.logicalpos, Some("[0 10000]"));
125        assert_eq!(editor.comments, Some("Test comment"));
126    }
127
128    #[test]
129    fn test_editor_minimal() {
130        let input = r#"
131        editor
132        {
133            "color" "255 0 0"
134            "visgroupshown" "1"
135            "visgroupautoshown" "1"
136        }
137        "#;
138
139        let stream = lex(input);
140        let result = EditorData::parse(stream);
141        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
142
143        let editor = result.unwrap();
144        assert_eq!(editor.color.r, 255);
145        assert_eq!(editor.color.g, 0);
146        assert_eq!(editor.color.b, 0);
147        assert_eq!(editor.visgroupshown, true);
148        assert_eq!(editor.visgroupautoshown, true);
149        assert_eq!(editor.logicalpos, None);
150        assert_eq!(editor.comments, None);
151    }
152
153    #[test]
154    fn test_editor_properties_out_of_order() {
155        let input = r#"
156        editor
157        {
158            "logicalpos" "[0 5000]"
159            "visgroupautoshown" "0"
160            "color" "100 200 50"
161            "comments" "Out of order test"
162            "visgroupshown" "0"
163        }
164        "#;
165
166        let stream = lex(input);
167        let result = EditorData::parse(stream);
168        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
169
170        let editor = result.unwrap();
171        assert_eq!(editor.color.r, 100);
172        assert_eq!(editor.color.g, 200);
173        assert_eq!(editor.color.b, 50);
174        assert_eq!(editor.visgroupshown, false);
175        assert_eq!(editor.visgroupautoshown, false);
176        assert_eq!(editor.logicalpos, Some("[0 5000]"));
177        assert_eq!(editor.comments, Some("Out of order test"));
178    }
179
180    #[test]
181    fn test_editor_with_logicalpos_only() {
182        let input = r#"
183        editor
184        {
185            "color" "128 128 128"
186            "visgroupshown" "1"
187            "visgroupautoshown" "1"
188            "logicalpos" "[0 0]"
189        }
190        "#;
191
192        let stream = lex(input);
193        let result = EditorData::parse(stream);
194        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
195
196        let editor = result.unwrap();
197        assert_eq!(editor.logicalpos, Some("[0 0]"));
198        assert_eq!(editor.comments, None);
199    }
200
201    #[test]
202    fn test_editor_with_comments_only() {
203        let input = r#"
204        editor
205        {
206            "color" "64 64 64"
207            "visgroupshown" "1"
208            "visgroupautoshown" "1"
209            "comments" "This brush needs work"
210        }
211        "#;
212
213        let stream = lex(input);
214        let result = EditorData::parse(stream);
215        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
216
217        let editor = result.unwrap();
218        assert_eq!(editor.comments, Some("This brush needs work"));
219        assert_eq!(editor.logicalpos, None);
220    }
221
222    #[test]
223    fn test_editor_empty_block() {
224        let input = r#"
225        editor
226        {
227        }
228        "#;
229
230        let stream = lex(input);
231        let result = EditorData::parse(stream);
232        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
233
234        let editor = result.unwrap();
235        let default = EditorData::default();
236        assert_eq!(editor.color.r, default.color.r);
237        assert_eq!(editor.visgroupshown, default.visgroupshown);
238        assert_eq!(editor.comments, None);
239    }
240
241    #[test]
242    fn test_editor_invalid_color() {
243        let input = r#"
244        editor
245        {
246            "color" "invalid color"
247            "visgroupshown" "1"
248            "visgroupautoshown" "1"
249        }
250        "#;
251
252        let stream = lex(input);
253        let result = EditorData::parse(stream);
254        assert!(result.is_err(), "Parser should fail on invalid color");
255    }
256
257    #[test]
258    fn test_editor_invalid_visgroupshown() {
259        let input = r#"
260        editor
261        {
262            "color" "255 255 255"
263            "visgroupshown" "not_a_bool"
264            "visgroupautoshown" "1"
265        }
266        "#;
267
268        let stream = lex(input);
269        let result = EditorData::parse(stream);
270        assert!(
271            result.is_err(),
272            "Parser should fail on invalid visgroupshown"
273        );
274    }
275
276    #[test]
277    fn test_editor_invalid_block_name() {
278        let input = r#"
279        wrongname
280        {
281            "color" "255 255 255"
282            "visgroupshown" "1"
283            "visgroupautoshown" "1"
284        }
285        "#;
286
287        let stream = lex(input);
288        let result = EditorData::parse(stream);
289        assert!(result.is_err(), "Parser should fail on invalid block name");
290    }
291
292    #[test]
293    fn test_editor_missing_closing_brace() {
294        let input = r#"
295        editor
296        {
297            "color" "255 255 255"
298            "visgroupshown" "1"
299            "visgroupautoshown" "1"
300        "#;
301
302        let stream = lex(input);
303        let result = EditorData::parse(stream);
304        assert!(
305            result.is_err(),
306            "Parser should fail on missing closing brace"
307        );
308    }
309
310    #[test]
311    fn test_editor_multiline_comments() {
312        let input = r#"
313        editor
314        {
315            "color" "50 100 150"
316            "visgroupshown" "1"
317            "visgroupautoshown" "1"
318            "comments" "This is a very long comment that describes the purpose of this brush in detail"
319        }
320        "#;
321
322        let stream = lex(input);
323        let result = EditorData::parse(stream);
324        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
325
326        let editor = result.unwrap();
327        assert_eq!(
328            editor.comments,
329            Some("This is a very long comment that describes the purpose of this brush in detail")
330        );
331    }
332
333    #[test]
334    fn test_editor_duplicate_properties_last_wins() {
335        let input = r#"
336        editor
337        {
338            "color" "100 100 100"
339            "visgroupshown" "0"
340            "visgroupshown" "1"
341            "color" "200 200 200"
342        }
343        "#;
344
345        let stream = lex(input);
346        let result = EditorData::parse(stream);
347        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
348
349        let editor = result.unwrap();
350        assert_eq!(editor.color.r, 200);
351        assert_eq!(editor.color.g, 200);
352        assert_eq!(editor.color.b, 200);
353        assert_eq!(editor.visgroupshown, true);
354    }
355}