pyreq_rs/parser/
version.rs

1//! refer to https://github.com/pypa/packaging/blob/main/src/packaging/version.py
2//! 正则忽略大小写
3//! this is version scheme, 与requirement_specifier中用的version string不同, 在specifier中还能用通配符*
4
5use nom::{
6    branch::alt,
7    bytes::complete::{tag_no_case, take_while_m_n},
8    character::complete::{alphanumeric1, char as nomchar, digit1, satisfy},
9    combinator::opt,
10    multi::many0,
11    sequence::{preceded, terminated, tuple},
12    IResult, Parser,
13};
14
15use crate::requirements::{LocalVersionPart, Version};
16
17pub fn epoch(input: &str) -> IResult<&str, u64> {
18    terminated(digit1, nomchar('!'))
19        .map(|s: &str| s.parse::<u64>().unwrap())
20        .parse(input)
21}
22
23pub fn release(input: &str) -> IResult<&str, Vec<u64>> {
24    digit1
25        .and(many0(preceded(nomchar('.'), digit1)))
26        .map(|(first, mut rest)| {
27            rest.insert(0, first);
28            rest.into_iter()
29                .map(|s: &str| s.parse::<u64>().unwrap())
30                .collect()
31        })
32        .parse(input)
33}
34
35// pre-release letter
36pub fn pre_l(input: &str) -> IResult<&str, &str> {
37    // 得把长的字符串往前放,不然匹配上a后,就不匹配后边的lpha了
38    alt((
39        tag_no_case("alpha"),
40        tag_no_case("preview"),
41        tag_no_case("beta"),
42        tag_no_case("a"),
43        tag_no_case("b"),
44        tag_no_case("c"),
45        tag_no_case("rc"),
46        tag_no_case("pre"),
47    ))(input)
48}
49
50//see _parse_letter_version from https://github.com/pypa/packaging/blob/main/src/packaging/version.py
51pub fn pre(input: &str) -> IResult<&str, (String, u64)> {
52    tuple((
53        take_while_m_n(0, 1, |c| "-_.".contains(c)),
54        pre_l,
55        take_while_m_n(0, 1, |c| "-_.".contains(c)),
56        opt(digit1),
57    ))
58    .map(|(_, l, _, n)| {
59        let number = n.map_or(0u64, |s| s.parse().unwrap());
60        let letter = l.to_lowercase();
61        match letter.as_str() {
62            "alpha" => ("a".to_string(), number),
63            "beta" => ("b".to_string(), number),
64            "c" | "pre" | "preview" => ("rc".to_string(), number),
65            _ => (letter, number),
66        }
67    })
68    .parse(input)
69}
70
71pub fn post_l(input: &str) -> IResult<&str, &str> {
72    alt((tag_no_case("post"), tag_no_case("rev"), tag_no_case("r")))(input)
73}
74
75pub fn post(input: &str) -> IResult<&str, (String, u64)> {
76    alt((
77        preceded(nomchar('-'), digit1)
78            .map(|s: &str| ("post".to_string(), s.parse::<u64>().unwrap())),
79        tuple((
80            take_while_m_n(0, 1, |c| "-_.".contains(c)),
81            post_l,
82            take_while_m_n(0, 1, |c| "-_.".contains(c)),
83            opt(digit1),
84        ))
85        .map(|(_, _, _, n)| ("post".to_string(), n.map_or(0u64, |s| s.parse().unwrap()))),
86    ))(input)
87}
88
89pub fn dev(input: &str) -> IResult<&str, (String, u64)> {
90    tuple((
91        take_while_m_n(0, 1, |c| "-_.".contains(c)),
92        tag_no_case("dev"),
93        take_while_m_n(0, 1, |c| "-_.".contains(c)),
94        opt(digit1),
95    ))
96    .map(|(_, _, _, n)| {
97        (
98            "dev".to_string(),
99            n.map_or(0u64, |s: &str| s.parse().unwrap()),
100        )
101    })
102    .parse(input)
103}
104
105// see _parse_local_version from https://github.com/pypa/packaging/blob/main/src/packaging/version.py
106pub fn local(input: &str) -> IResult<&str, Vec<LocalVersionPart>> {
107    preceded(
108        nomchar('+'),
109        alphanumeric1
110            .and(many0(preceded(
111                satisfy(|c| "-_.".contains(c)),
112                alphanumeric1,
113            )))
114            .map(|(first, mut rest)| {
115                rest.insert(0, first);
116                rest.into_iter()
117                    .map(|s: &str| match s.parse::<u64>() {
118                        Ok(n) => LocalVersionPart::Num(n),
119                        Err(_) => LocalVersionPart::LowerStr(s.to_lowercase()),
120                    })
121                    .collect()
122            }),
123    )(input)
124}
125
126pub fn version_scheme(input: &str) -> IResult<&str, Version> {
127    tuple((
128        opt(nomchar('v')),
129        opt(epoch),
130        release,
131        opt(pre),
132        opt(post),
133        opt(dev),
134        opt(local),
135    ))
136    .map(|(_, e, r, p, po, de, lo)| Version {
137        epoch: e.unwrap_or(0u64),
138        release: r,
139        pre: p,
140        post: po,
141        dev: de,
142        local: lo,
143    })
144    .parse(input)
145}