version_spec/
unresolved_parser.rs

1use crate::spec_error::SpecError;
2use human_sort::compare;
3
4#[derive(Debug, Default, PartialEq)]
5pub enum ParseKind {
6    #[default]
7    Unknown,
8    Req,
9    Cal,
10    Sem,
11}
12
13#[derive(Debug, Default, PartialEq)]
14pub enum ParsePart {
15    #[default]
16    Start,
17    ReqPrefix,
18    MajorYear,
19    MinorMonth,
20    PatchDay,
21    PreId,
22    BuildSuffix,
23}
24
25impl ParsePart {
26    pub fn is_prefix(&self) -> bool {
27        matches!(self, Self::Start | Self::ReqPrefix)
28    }
29
30    pub fn is_suffix(&self) -> bool {
31        matches!(self, Self::PreId | Self::BuildSuffix)
32    }
33}
34
35#[derive(Debug, Default)]
36pub struct UnresolvedParser {
37    // States
38    kind: ParseKind,
39    in_part: ParsePart,
40    is_and: bool,
41    req_op: String,
42    major_year: String,
43    minor_month: String,
44    patch_day: String,
45    pre_id: String,
46    build_id: String,
47
48    // Final result
49    results: Vec<String>,
50}
51
52impl UnresolvedParser {
53    pub fn parse(mut self, input: impl AsRef<str>) -> Result<(String, ParseKind), SpecError> {
54        let input = input.as_ref().trim();
55
56        if input.is_empty() || input == "*" {
57            return Ok(("*".to_owned(), ParseKind::Req));
58        }
59
60        for ch in input.chars() {
61            match ch {
62                // Requirement operator
63                '=' | '~' | '^' | '>' | '<' => {
64                    if self.in_part != ParsePart::Start && self.in_part != ParsePart::ReqPrefix {
65                        return Err(SpecError::InvalidParseRequirement);
66                    }
67
68                    self.in_part = ParsePart::ReqPrefix;
69                    self.req_op.push(ch);
70                }
71                // Wildcard operator
72                '*' => {
73                    // Ignore entirely
74                }
75                // Version part
76                '0'..='9' => {
77                    let part_str = match self.in_part {
78                        ParsePart::Start | ParsePart::ReqPrefix | ParsePart::MajorYear => {
79                            self.in_part = ParsePart::MajorYear;
80                            &mut self.major_year
81                        }
82                        ParsePart::MinorMonth => &mut self.minor_month,
83                        ParsePart::PatchDay => &mut self.patch_day,
84                        ParsePart::PreId => &mut self.pre_id,
85                        ParsePart::BuildSuffix => &mut self.build_id,
86                    };
87
88                    part_str.push(ch);
89                }
90                // Suffix part
91                'a'..='z' | 'A'..='Z' => match self.in_part {
92                    ParsePart::PreId => {
93                        self.pre_id.push(ch);
94                    }
95                    ParsePart::BuildSuffix => {
96                        self.build_id.push(ch);
97                    }
98                    _ => {
99                        // Remove leading v
100                        if ch == 'v' || ch == 'V' {
101                            continue;
102                        } else {
103                            return Err(SpecError::UnknownParseChar(ch));
104                        }
105                    }
106                },
107                // Part separator
108                '.' | '-' => {
109                    // Determine version type based on separator
110                    if self.kind == ParseKind::Unknown {
111                        if ch == '-' {
112                            self.kind = ParseKind::Cal;
113                        } else {
114                            self.kind = ParseKind::Sem;
115                        }
116                    }
117
118                    // Continue to the next part
119                    if ch == '-' {
120                        if self.kind == ParseKind::Sem {
121                            match self.in_part {
122                                ParsePart::MajorYear
123                                | ParsePart::MinorMonth
124                                | ParsePart::PatchDay => {
125                                    self.in_part = ParsePart::PreId;
126                                }
127                                ParsePart::PreId => {
128                                    self.pre_id.push('-');
129                                }
130                                ParsePart::BuildSuffix => {
131                                    self.build_id.push('-');
132                                }
133                                _ => continue,
134                            };
135                        } else if self.kind == ParseKind::Cal {
136                            match self.in_part {
137                                ParsePart::MajorYear => {
138                                    self.in_part = ParsePart::MinorMonth;
139                                }
140                                ParsePart::MinorMonth => {
141                                    self.in_part = ParsePart::PatchDay;
142                                }
143                                ParsePart::PatchDay | ParsePart::BuildSuffix => {
144                                    self.in_part = ParsePart::PreId;
145                                }
146                                ParsePart::PreId => {
147                                    self.pre_id.push('-');
148                                }
149                                _ => continue,
150                            };
151                        }
152                    } else if ch == '.' {
153                        if self.kind == ParseKind::Sem {
154                            match self.in_part {
155                                ParsePart::MajorYear => {
156                                    self.in_part = ParsePart::MinorMonth;
157                                }
158                                ParsePart::MinorMonth => {
159                                    self.in_part = ParsePart::PatchDay;
160                                }
161                                ParsePart::PatchDay => {
162                                    self.in_part = ParsePart::PreId;
163                                }
164                                ParsePart::PreId => {
165                                    self.pre_id.push('.');
166                                }
167                                ParsePart::BuildSuffix => {
168                                    self.build_id.push('.');
169                                }
170                                _ => continue,
171                            };
172                        } else if self.kind == ParseKind::Cal {
173                            match self.in_part {
174                                ParsePart::MajorYear
175                                | ParsePart::MinorMonth
176                                | ParsePart::PatchDay => {
177                                    self.in_part = ParsePart::BuildSuffix;
178                                }
179                                ParsePart::PreId => {
180                                    self.pre_id.push('.');
181                                }
182                                ParsePart::BuildSuffix => {
183                                    self.build_id.push('.');
184                                }
185                                _ => continue,
186                            };
187                        }
188                    }
189                }
190                // Build separator
191                '_' | '+' => {
192                    if ch == '+' {
193                        if self.kind == ParseKind::Sem {
194                            self.in_part = ParsePart::BuildSuffix;
195                        } else {
196                            continue;
197                        }
198                    } else if self.kind == ParseKind::Cal {
199                        self.in_part = ParsePart::BuildSuffix;
200                    } else {
201                        continue;
202                    }
203                }
204                // AND separator
205                ',' => {
206                    self.is_and = true;
207                    self.build_result()?;
208                    self.reset_state();
209                }
210                // Whitespace
211                ' ' => {
212                    if self.in_part.is_prefix() {
213                        // Skip
214                    } else {
215                        // Possible AND sequence?
216                        self.is_and = true;
217                        self.build_result()?;
218                        self.reset_state();
219                    }
220                }
221                _ => {
222                    return Err(SpecError::UnknownParseChar(ch));
223                }
224            }
225        }
226
227        self.build_result()?;
228
229        let result = self.get_result();
230        let is_req = result.contains(',');
231
232        Ok((result, if is_req { ParseKind::Req } else { self.kind }))
233    }
234
235    fn get_result(&self) -> String {
236        self.results.join(",")
237    }
238
239    fn get_part<'p>(&self, value: &'p str) -> &'p str {
240        let value = value.trim_start_matches('0');
241
242        if value.is_empty() {
243            return "0";
244        }
245
246        value
247    }
248
249    fn build_result(&mut self) -> Result<(), SpecError> {
250        if self.in_part.is_prefix() {
251            return Ok(());
252        }
253
254        let mut output = String::new();
255        let was_calver = self.kind == ParseKind::Cal;
256
257        if self.req_op.is_empty() {
258            if self.minor_month.is_empty() || self.patch_day.is_empty() {
259                self.kind = ParseKind::Req;
260
261                if !self.is_and {
262                    output.push('~');
263                }
264            }
265        } else {
266            self.kind = ParseKind::Req;
267            output.push_str(&self.req_op);
268        }
269
270        let separator = if self.kind == ParseKind::Cal && !self.is_and {
271            '-'
272        } else {
273            '.'
274        };
275
276        // Major/year
277        if was_calver {
278            let year = self.get_part(&self.major_year);
279
280            if year.len() < 4 {
281                let mut year: usize = year.parse().unwrap();
282                year += 2000;
283
284                output.push_str(&year.to_string());
285            } else {
286                output.push_str(year);
287            }
288        } else if self.major_year.is_empty() {
289            return Err(SpecError::MissingParseMajorPart);
290        } else {
291            output.push_str(self.get_part(&self.major_year));
292        }
293
294        // Minor/month
295        if !self.minor_month.is_empty() {
296            output.push(separator);
297            output.push_str(self.get_part(&self.minor_month));
298        }
299
300        // Patch/day
301        if !self.patch_day.is_empty() {
302            output.push(separator);
303            output.push_str(self.get_part(&self.patch_day));
304        }
305
306        // Pre ID
307        if !self.pre_id.is_empty() {
308            output.push('-');
309            output.push_str(&self.pre_id);
310        }
311
312        // Build metadata
313        if !self.build_id.is_empty() {
314            output.push('+');
315            output.push_str(&self.build_id);
316        }
317
318        self.results.push(output);
319
320        Ok(())
321    }
322
323    fn reset_state(&mut self) {
324        self.kind = ParseKind::Unknown;
325        self.in_part = ParsePart::Start;
326        self.req_op.truncate(0);
327        self.major_year.truncate(0);
328        self.minor_month.truncate(0);
329        self.patch_day.truncate(0);
330        self.pre_id.truncate(0);
331        self.build_id.truncate(0);
332    }
333}
334
335/// Parse the provided string as a list of version requirements,
336/// as separated by `||`. Each requirement will be parsed
337/// individually with [`parse`].
338pub fn parse_multi(input: impl AsRef<str>) -> Result<Vec<String>, SpecError> {
339    let input = input.as_ref();
340    let mut results = vec![];
341
342    if input.contains("||") {
343        let mut parts = input.split("||").map(|p| p.trim()).collect::<Vec<_>>();
344
345        // Try and sort from highest to lowest range
346        parts.sort_by(|a, d| compare(d, a));
347
348        for part in parts {
349            results.push(parse(part)?.0);
350        }
351    } else {
352        results.push(parse(input)?.0);
353    }
354
355    Ok(results)
356}
357
358/// Parse the provided string and determine the output format.
359/// Since an unresolved version can be many things, such as an
360/// alias, version requirement, semver, or calver, we need to
361/// parse this manually to determine the correct output.
362pub fn parse(input: impl AsRef<str>) -> Result<(String, ParseKind), SpecError> {
363    UnresolvedParser::default().parse(input)
364}