Skip to main content

rabex/
unity_version.rs

1use std::str::FromStr;
2
3use num_enum::TryFromPrimitive;
4
5/// A parsed unity version.
6///
7/// Example: `2023.2.18f1`
8#[derive(Clone, PartialEq, PartialOrd)]
9pub struct UnityVersion {
10    pub major: u16,
11    pub minor: u16,
12    pub build: u16,
13    pub typ: UnityVersionType,
14    pub build_number: u8,
15    pub trailing_data: String,
16}
17
18impl UnityVersion {
19    /// major.minor.buildf1
20    pub fn new(major: u16, minor: u16, build: u16) -> Self {
21        UnityVersion {
22            major,
23            minor,
24            build,
25            typ: UnityVersionType::Final,
26            build_number: 1,
27            trailing_data: String::new(),
28        }
29    }
30
31    pub fn version_tuple(&self) -> (u16, u16, u16) {
32        (self.major, self.minor, self.build)
33    }
34}
35
36impl std::fmt::Debug for UnityVersion {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        std::fmt::Display::fmt(self, f)
39    }
40}
41
42impl std::fmt::Display for UnityVersion {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(
45            f,
46            "{}.{}.{}{}{}",
47            self.major,
48            self.minor,
49            self.build,
50            self.typ.char(),
51            self.build_number,
52        )?;
53        if !self.trailing_data.is_empty() {
54            write!(f, "-{}", self.trailing_data)?;
55        }
56        Ok(())
57    }
58}
59
60#[non_exhaustive]
61#[derive(Debug)]
62pub struct UnityVersionParseError(String);
63
64impl std::fmt::Display for UnityVersionParseError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(f, "Could not parse unity version {}", self.0)
67    }
68}
69
70impl std::error::Error for UnityVersionParseError {}
71
72impl FromStr for UnityVersion {
73    type Err = UnityVersionParseError;
74
75    fn from_str(s: &str) -> Result<Self, Self::Err> {
76        // TODO: reproduce 2020.2.2f1\n2
77        (|| {
78            let mut split = s.split('.');
79            let major = split.next()?.parse().ok()?;
80            let minor = split.next()?.parse().ok()?;
81            let rest = split.next()?;
82
83            let i = rest.find(char::is_alphabetic)?;
84            let build = &rest[..i];
85            let typ = match rest.as_bytes()[i] as char {
86                'a' => UnityVersionType::Alpha,
87                'b' => UnityVersionType::Beta,
88                // TODO china
89                'p' => UnityVersionType::Patch,
90                'f' => UnityVersionType::Final,
91                // TODO experimental
92                _ => return None,
93            };
94            let rest = &rest[i + 1..];
95
96            let (build_number, trailing_data) = rest.split_once('-').unwrap_or((rest, ""));
97
98            Some(UnityVersion {
99                major,
100                minor,
101                build: build.parse().ok()?,
102                typ,
103                build_number: build_number.parse().ok()?,
104                trailing_data: trailing_data.to_owned(),
105            })
106        })()
107        .ok_or_else(|| UnityVersionParseError(s.to_owned()))
108    }
109}
110
111#[repr(u8)]
112#[derive(Copy, Clone, Debug, TryFromPrimitive, PartialEq, PartialOrd)]
113pub enum UnityVersionType {
114    Alpha = 0,
115    Beta = 1,
116    China = 2,
117    Final = 3,
118    Patch = 4,
119    Experimental = 5,
120}
121
122impl UnityVersionType {
123    pub fn char(self) -> char {
124        match self {
125            UnityVersionType::Alpha => 'a',
126            UnityVersionType::Beta => 'b',
127            UnityVersionType::China => 'c', // ?
128            UnityVersionType::Final => 'f',
129            UnityVersionType::Patch => 'p',        // ?
130            UnityVersionType::Experimental => 'e', // ?
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use crate::{UnityVersion, UnityVersionType};
138
139    #[test]
140    fn unity_version_simple() {
141        let version: UnityVersion = "2022.2.2a13".parse().unwrap();
142
143        assert_eq!(
144            version,
145            UnityVersion {
146                major: 2022,
147                minor: 2,
148                build: 2,
149                typ: UnityVersionType::Alpha,
150                build_number: 13,
151                trailing_data: "".into(),
152            }
153        )
154    }
155
156    #[test]
157    fn unity_version_complex() {
158        let version: UnityVersion = "6000.0.50f1-uum-100966-branch1".parse().unwrap();
159
160        assert_eq!(
161            version,
162            UnityVersion {
163                major: 6000,
164                minor: 0,
165                build: 50,
166                typ: UnityVersionType::Final,
167                build_number: 1,
168                trailing_data: "uum-100966-branch1".into()
169            }
170        )
171    }
172}