Skip to main content

mnk_vmf/types/
versioninfo.rs

1use chumsky::Parser as ChumskyParser;
2
3use crate::parser::{
4    close_block, key_value_numeric, open_block, InternalParser, Parser, TokenError, TokenSource,
5};
6
7/// `VersionInfo` holds the VMF Header information.
8#[derive(Clone, Debug)]
9pub struct VersionInfo {
10    pub editor_version: u32,
11    pub editor_build: u32,
12    pub map_version: u16,
13    pub format_version: u16,
14    pub prefab: u32,
15}
16
17impl VersionInfo {
18    /// Crates a new [`VersionInfo`] instance.
19    pub fn new(
20        version: u32,
21        build: u32,
22        map_version: u16,
23        format_version: u16,
24        prefab: u32,
25    ) -> VersionInfo {
26        Self {
27            editor_version: version,
28            editor_build: build,
29            map_version,
30            format_version,
31            prefab,
32        }
33    }
34}
35
36/// Public parser trait implementation that allows [`VersionInfo`] to use ::parse(input) call.
37impl Parser<'_> for VersionInfo {}
38
39/// A [`InternalParser`] implementation for [`VersionInfo`].
40/// Every key-value pair needs to be in order, like in the example bellow.
41///
42/// usage: `let version_info = VersionInfo::parser().parse();`.
43///
44/// The format that is being parsed here is:
45/// versioninfo
46/// {
47/// "editorversion" "400"
48/// "editorbuild" "6157"
49/// "mapversion" "16"
50/// "formatversion" "100"
51/// "prefab" "0"
52/// }
53impl<'src> InternalParser<'src> for VersionInfo {
54    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
55    where
56        I: TokenSource<'src>,
57    {
58        open_block("versioninfo")
59            .ignored()
60            .then(key_value_numeric::<u32, I>("editorversion"))
61            .then(key_value_numeric::<u32, I>("editorbuild"))
62            .then(key_value_numeric::<u16, I>("mapversion"))
63            .then(key_value_numeric::<u16, I>("formatversion"))
64            .then(key_value_numeric::<u32, I>("prefab"))
65            .map(|(((((_, vi), eb), mv), fv), pf)| VersionInfo::new(vi, eb, mv, fv, pf))
66            .then_ignore(close_block())
67            .boxed()
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use crate::util::lex;
74
75    use super::*;
76    use chumsky::Parser;
77
78    #[test]
79    fn test_version_info_parser() {
80        // Valid input
81        let input = lex(r#"versioninfo
82                    {
83                        "editorversion" "400"
84                        "editorbuild" "6157"
85                        "mapversion" "16"
86                        "formatversion" "100"
87                        "prefab" "0"
88                    }"#);
89
90        let result = VersionInfo::parser().parse(input);
91        assert!(
92            !result.has_errors(),
93            "Parser failed with error: {:?}",
94            result.errors().collect::<Vec<_>>()
95        );
96
97        let version_info = result.unwrap();
98        assert_eq!(version_info.editor_version, 400);
99        assert_eq!(version_info.editor_build, 6157);
100        assert_eq!(version_info.map_version, 16);
101        assert_eq!(version_info.format_version, 100);
102        assert_eq!(version_info.prefab, 0);
103
104        // Test with different whitespace patterns
105        let compact_input = lex(
106            r#"versioninfo{"editorversion""500""editorbuild""7000""mapversion""20""formatversion""110""prefab""1"}"#,
107        );
108        let compact_result = VersionInfo::parser().parse(compact_input);
109        assert!(
110            !compact_result.has_errors(),
111            "Compact parser failed with error: {:?}",
112            compact_result.errors().collect::<Vec<_>>()
113        );
114
115        // Test with invalid input - missing field
116        let missing_field = lex(r#"versioninfo
117                                    {
118                                        "editorversion" "400"
119                                        "editorbuild" "6157"
120                                        "mapversion" "16"
121                                        "prefab" "0"
122                                    }"#); // Missing formatversion
123
124        let missing_result = VersionInfo::parser().parse(missing_field);
125        assert!(
126            missing_result.has_errors(),
127            "Parser should fail on missing field"
128        );
129
130        // Test with invalid input - invalid number format
131        let invalid_format = lex(r#"versioninfo
132                                    {
133                                        "editorversion" "400"
134                                        "editorbuild" "invalid"
135                                        "mapversion" "16"
136                                        "formatversion" "100"
137                                        "prefab" "0"
138                                    }"#);
139
140        let invalid_result = VersionInfo::parser().parse(invalid_format);
141        assert!(
142            invalid_result.has_errors(),
143            "Parser should fail on invalid number format"
144        );
145    }
146}