scl_core/
semver.rs

1//! 解析版本号
2use std::{
3    cmp::Ordering,
4    fmt::{Display, Formatter},
5    str::FromStr,
6};
7
8use nom::*;
9
10/// 一个用于表达当前 Minecraft 版本号的枚举结构
11///
12/// 用来根据版本判断使用不同的安装方式,还有相关的资源获取等
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
14pub enum MinecraftVersion {
15    /// 正式版本的版本号
16    Release(u32, u32, u32),
17    /// 快照版本的版本号
18    Snapshot(u32, u32, char),
19    /// 一些特殊版本的版本号,有可能是 Beta 或者 Alpha 等远古版本或被特殊命名的版本
20    Custom(String),
21}
22
23impl Default for MinecraftVersion {
24    fn default() -> Self {
25        Self::Custom("".into())
26    }
27}
28
29impl FromStr for MinecraftVersion {
30    type Err = ();
31    fn from_str(s: &str) -> Result<Self, Self::Err> {
32        parse_version(s).map(|a| a.1).or(Err(()))
33    }
34}
35
36impl Display for MinecraftVersion {
37    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38        match self {
39            MinecraftVersion::Release(a, b, c) => {
40                if c == &0 {
41                    write!(f, "{a}.{b}")
42                } else {
43                    write!(f, "{a}.{b}.{c}")
44                }
45            }
46            MinecraftVersion::Snapshot(a, b, c) => write!(f, "{a:02}w{b:02}{c}"),
47            Self::Custom(c) => write!(f, "{c}"),
48        }
49    }
50}
51
52impl MinecraftVersion {
53    /// 检查该版本需要的最低 Java 版本  
54    /// 目前检测到 1.17+ 的正式版本都会返回 16,其余的返回 8
55    pub fn required_java_version(&self) -> u8 {
56        if let Self::Release(mayor, minor, _) = *self {
57            if mayor >= 1 && minor >= 17 {
58                16
59            } else {
60                8
61            }
62        } else {
63            8
64        }
65    }
66
67    /// 确认如果该版本需要安装 Forge,是否使用覆盖 minecraft.jar 的方式进行安装
68    ///
69    /// 一般在版本为 1.5.1 或者更早时为 `true`
70    ///
71    /// 如果为 `false` 则使用 Forge 安装器安装
72    pub fn should_forge_use_override_installiation(&self) -> bool {
73        if let Self::Release(a, b, c) = self {
74            match a.cmp(&1) {
75                Ordering::Greater => false,
76                Ordering::Equal => match b.cmp(&1) {
77                    Ordering::Greater => false,
78                    Ordering::Equal => *c < 2,
79                    Ordering::Less => true,
80                },
81                Ordering::Less => true,
82            }
83        } else {
84            true
85        }
86    }
87
88    /// 确认如果该版本需要安装 Forge,下载的文件名尾缀是否是 `client` 还是 `universal`
89    ///
90    /// 一般在版本为 1.2.5 或者更早时为 `true`
91    ///
92    /// 如果为 `false` 则使用 `universal` 作为尾缀
93    pub fn should_forge_use_client_or_universal(&self) -> bool {
94        if let Self::Release(a, b, c) = self {
95            match a.cmp(&1) {
96                Ordering::Greater => false,
97                Ordering::Equal => match b.cmp(&2) {
98                    Ordering::Greater => false,
99                    Ordering::Equal => *c <= 5,
100                    Ordering::Less => true,
101                },
102                Ordering::Less => true,
103            }
104        } else {
105            true
106        }
107    }
108}
109
110/// 尝试解析版本字符串,并转换成 [`MinecraftVersion`] 枚举类型
111pub fn parse_version(input: &str) -> IResult<&str, MinecraftVersion> {
112    let (input, first_number) = character::complete::digit1(input)?;
113    let first_number = first_number.parse::<u32>().unwrap();
114    let (input, s) = character::complete::one_of(".w")(input)?;
115    match s {
116        '.' => {
117            // Release
118            let (input, second_number) = character::complete::digit1(input)?;
119            let second_number = second_number.parse::<u32>().unwrap();
120            if input.is_empty() {
121                return Ok((
122                    input,
123                    MinecraftVersion::Release(first_number, second_number, 0),
124                ));
125            }
126            let (input, _) = character::complete::char('.')(input)?;
127            let (input, third_number) = character::complete::digit1(input)?;
128            let third_number = third_number.parse::<u32>().unwrap();
129            Ok((
130                input,
131                MinecraftVersion::Release(first_number, second_number, third_number),
132            ))
133        }
134        'w' => {
135            // Snapshot
136            let (input, second_number) = character::complete::digit1(input)?;
137            let second_number = second_number.parse::<u32>().unwrap();
138            let (input, tag_alpha) = character::complete::anychar(input)?;
139            Ok((
140                input,
141                MinecraftVersion::Snapshot(first_number, second_number, tag_alpha),
142            ))
143        }
144        _ => {
145            panic!("Version dot is not correct!")
146        }
147    }
148}
149
150#[test]
151fn parse_version_test() {
152    assert_eq!(
153        parse_version("1.16.5").unwrap().1,
154        MinecraftVersion::Release(1, 16, 5)
155    );
156    assert_eq!(
157        parse_version("1.8").unwrap().1,
158        MinecraftVersion::Release(1, 8, 0)
159    );
160    assert_eq!(
161        parse_version("21w08b").unwrap().1,
162        MinecraftVersion::Snapshot(21, 8, 'b')
163    );
164    assert_eq!(
165        "1.16.5".parse::<MinecraftVersion>().unwrap(),
166        MinecraftVersion::Release(1, 16, 5)
167    );
168    assert_eq!(
169        "1.8".parse::<MinecraftVersion>().unwrap(),
170        MinecraftVersion::Release(1, 8, 0)
171    );
172    assert_eq!(
173        "21w08b".parse::<MinecraftVersion>().unwrap(),
174        MinecraftVersion::Snapshot(21, 8, 'b')
175    );
176    assert_eq!(&MinecraftVersion::Release(1, 16, 5).to_string(), "1.16.5");
177    assert_eq!(&MinecraftVersion::Release(1, 8, 0).to_string(), "1.8");
178    assert_eq!(
179        &MinecraftVersion::Snapshot(21, 8, 'b').to_string(),
180        "21w08b"
181    );
182    assert!(MinecraftVersion::Release(1, 16, 5).required_java_version() >= 8);
183    assert!(MinecraftVersion::Release(1, 17, 1).required_java_version() >= 16);
184    assert!(MinecraftVersion::Release(1, 17, 0).required_java_version() >= 16);
185}