Skip to main content

mnk_vmf/types/
visgroup.rs

1use chumsky::{prelude::recursive, IterParser, Parser as ChumskyParser};
2
3use crate::{
4    parser::{
5        any_quoted_string, close_block, number, open_block, quoted_string, InternalParser,
6        TokenError, TokenSource,
7    },
8    types::Color,
9    Parser,
10};
11
12/// Represents a visgroup in the VMF file
13/// Visgroups can be nested and contain properties like name, id, and color
14#[derive(Debug, Clone, PartialEq)]
15pub struct VisGroup<'a> {
16    /// The name of the visgroup
17    name: &'a str,
18
19    /// The unique identifier for the visgroup
20    visgroupid: u32,
21
22    /// The color of the visgroup in RGB format
23    color: Color,
24
25    /// Child visgroups contained within this visgroup
26    children: Vec<VisGroup<'a>>,
27}
28
29impl<'a> VisGroup<'a> {
30    /// Crates a new [`VisGroup`] instance.
31    pub fn new(
32        name: &'a str,
33        visgroupid: u32,
34        color: Color,
35        children: Vec<VisGroup<'a>>,
36    ) -> VisGroup<'a> {
37        VisGroup {
38            name,
39            visgroupid,
40            color,
41            children,
42        }
43    }
44}
45
46#[derive(Debug, Clone, PartialEq)]
47pub struct VisGroups<'a>(Vec<VisGroup<'a>>);
48
49impl<'a> VisGroups<'a> {
50    pub fn new(visgroups: Vec<VisGroup<'a>>) -> VisGroups<'a> {
51        Self(visgroups)
52    }
53}
54
55/// Public parser trait implementation that allows [`VisGroups`] to use ::parse(input) call.
56impl<'src> Parser<'src> for VisGroups<'src> {}
57
58/// A [`InternalParser`] implementation for [`VisGroups`].
59/// Every key-value pair needs to be in order, like in the example bellow.
60///
61/// usage:
62/// ```ignore
63///     let visgroups = VisGroups::parser().parse();
64/// ```
65///
66/// The format that is being parsed here is:
67/// ```ignore
68/// visgroups
69/// {
70///
71///     visgroup
72///     {
73///        ...
74///     }
75///
76///     visgroup
77///     {
78///         ...
79///         visgroup
80///         {
81///             ...
82///         }
83///     }
84/// }
85/// ```
86impl<'src> InternalParser<'src> for VisGroups<'src> {
87    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
88    where
89        I: TokenSource<'src>,
90    {
91        open_block("visgroups")
92            .ignore_then(VisGroup::parser::<I>().repeated().collect())
93            .then_ignore(close_block())
94            .map(VisGroups::new)
95    }
96}
97
98/// Public parser trait implementation that allows [`VisGroup`] to use ::parse(input) call.
99impl<'src> Parser<'src> for VisGroup<'src> {}
100
101/// A [`InternalParser`] implementation for [`VisGroup`].
102/// Every key-value pair needs to be in order, like in the example bellow.
103///
104/// usage: `let visgroup = VisGroup::parser().parse();`.
105///
106/// The format that is being parsed here is:
107/// ```ignore
108/// visgroup
109/// {
110///     "name" "Tree_1"
111///     "visgroupid" "5"
112///     "color" "65 45 0"
113/// }
114/// ```
115impl<'src> InternalParser<'src> for VisGroup<'src> {
116    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
117    where
118        I: TokenSource<'src>,
119    {
120        recursive(|vis_group| {
121            open_block("visgroup")
122                .boxed()
123                .ignore_then(
124                    quoted_string("name")
125                        .boxed()
126                        .ignore_then(any_quoted_string().boxed())
127                        .then_ignore(quoted_string("visgroupid").boxed())
128                        .then(number::<u32, I>().boxed())
129                        .then(Color::parser::<I>().boxed())
130                        .then(vis_group.repeated().collect().boxed()),
131                )
132                .then_ignore(close_block().boxed())
133                .map(|(((name, id), color), children)| VisGroup::new(name, id, color, children))
134        })
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::{parser::util::lex, Parser};
142
143    #[test]
144    fn test_single_visgroup() {
145        let input = lex(r#"
146            visgroup {
147                "name" "Tree_1"
148                "visgroupid" "5"
149                "color" "65 45 0"
150            }
151        "#);
152
153        let parsed = VisGroup::parse(input).unwrap();
154        assert_eq!(parsed.name, "Tree_1");
155        assert_eq!(parsed.visgroupid, 5);
156        assert_eq!(parsed.color.r, 65);
157        assert_eq!(parsed.color.g, 45);
158        assert_eq!(parsed.color.b, 0);
159        assert!(parsed.children.is_empty());
160    }
161
162    #[test]
163    fn test_nested_visgroup() {
164        let input = lex(r#"
165            visgroup {
166                "name" "Parent"
167                "visgroupid" "1"
168                "color" "10 20 30"
169                visgroup {
170                    "name" "Child"
171                    "visgroupid" "2"
172                    "color" "100 100 100"
173                }
174            }
175        "#);
176
177        let parsed = VisGroup::parse(input).unwrap();
178        assert_eq!(parsed.name, "Parent");
179        assert_eq!(parsed.children.len(), 1);
180        assert_eq!(parsed.children[0].name, "Child");
181        assert_eq!(parsed.children[0].color.r, 100);
182    }
183
184    #[test]
185    fn test_visgroups_block() {
186        let input = lex(r#"
187            visgroups {
188                visgroup {
189                    "name" "One"
190                    "visgroupid" "11"
191                    "color" "11 22 33"
192                }
193                visgroup {
194                    "name" "Two"
195                    "visgroupid" "12"
196                    "color" "44 55 66"
197                }
198            }
199        "#);
200
201        let parsed = VisGroups::parse(input).unwrap();
202        assert_eq!(parsed.0.len(), 2);
203        assert_eq!(parsed.0[0].name, "One");
204        assert_eq!(parsed.0[1].name, "Two");
205    }
206
207    #[test]
208    fn test_empty_visgroups() {
209        let input = lex(r#"
210            visgroups {
211            }
212        "#);
213
214        let parsed = VisGroups::parse(input).unwrap();
215        assert_eq!(parsed.0.len(), 0);
216    }
217}