z_mirror/fmm

git clone https://git.raiguard.me/z_mirror/fmm

raiguardFix version reqs not being re-combined67c3f36

dependencies
3.8 KiB99 linesraw
1use itertools::Itertools;
2use once_cell::sync::OnceCell;
3use regex::Regex;
4use semver::VersionReq;
5use thiserror::Error;
6
7#[derive(Debug, PartialEq)]
8pub struct ModDependency {
9    pub dep_type: ModDependencyType,
10    pub name: String,
11    pub version_req: Option<VersionReq>,
12}
13
14impl ModDependency {
15    pub fn new(input: &String) -> Result<Self, ModDependencyErr> {
16        // Avoid creating the regex object every time
17        static DEP_STRING_REGEX: OnceCell<Regex> = OnceCell::new();
18        let captures = DEP_STRING_REGEX
19            .get_or_init(|| {
20                Regex::new(
21                    r"^(?:(?P<type>[!?~]|\(\?\)) *)?(?P<name>(?: *[a-zA-Z0-9_-]+)+(?: *$)?)(?: *(?P<version_req>[<>=]=?) *(?P<version>(?:\d+\.){1,2}\d+))?$",
22                ).unwrap()
23            })
24            .captures(input)
25            .ok_or(ModDependencyErr::InvalidDependencyString(input.clone()))?;
26
27        Ok(Self {
28            dep_type: match captures.name("type").map(|mtch| mtch.as_str()) {
29                None => ModDependencyType::Required,
30                Some("!") => ModDependencyType::Incompatible,
31                Some("~") => ModDependencyType::NoLoadOrder,
32                Some("?") => ModDependencyType::Optional,
33                Some("(?)") => ModDependencyType::OptionalHidden,
34                Some(str) => return Err(ModDependencyErr::UnknownModifier(str.to_string())),
35            },
36            name: match captures.name("name") {
37                Some(mtch) => mtch.as_str().to_string(),
38                None => return Err(ModDependencyErr::NameIsUnparsable(input.clone())),
39            },
40            version_req: match [captures.name("version_req"), captures.name("version")] {
41                [Some(req_match), Some(version_match)] => {
42                    // Factorio does not sanitize leading zeros, so we must do it ourselves
43                    let version_str = version_match.as_str();
44                    let sanitized = version_str
45                        .split('.')
46                        .map(|sub| {
47                            Ok(sub
48                                .parse::<usize>()
49                                .map_err(|_| {
50                                    ModDependencyErr::InvalidDependencyString(input.clone())
51                                })?
52                                .to_string())
53                        })
54                        .intersperse(Ok(".".to_string()))
55                        .collect::<Result<String, ModDependencyErr>>()?;
56
57                    // Assemble version req from parts
58                    let mut version_req = String::new();
59                    version_req.push_str(req_match.as_str());
60                    version_req.push(' ');
61                    version_req.push_str(&sanitized);
62
63                    match VersionReq::parse(&version_req) {
64                        Ok(version_req) => Some(version_req),
65                        Err(_) => {
66                            return Err(ModDependencyErr::InvalidVersionReq(
67                                req_match.as_str().to_string(),
68                            ))
69                        }
70                    }
71                }
72                _ => None,
73            },
74        })
75    }
76}
77
78#[derive(Debug, PartialEq)]
79pub enum ModDependencyType {
80    Incompatible,
81    NoLoadOrder,
82    Optional,
83    OptionalHidden,
84    Required,
85}
86
87pub type ModDependencyResult = Result<Vec<ModDependency>, ModDependencyErr>;
88
89#[derive(Clone, Debug, Error)]
90pub enum ModDependencyErr {
91    #[error("Invalid dependency string: `{0}`")]
92    InvalidDependencyString(String),
93    #[error("Invalid dependency version requirement: `{0}`")]
94    InvalidVersionReq(String),
95    #[error("Dependency name could not be parsed: `{0}`")]
96    NameIsUnparsable(String),
97    #[error("Unknown dependency modifier: `{0}`")]
98    UnknownModifier(String),
99}