Skip to main content

lux_lib/package/
version.rs

1use std::{
2    cmp::{self, Ordering},
3    fmt::Display,
4    str::FromStr,
5};
6
7use html_escape::decode_html_entities;
8use itertools::Itertools;
9
10use miette::Diagnostic;
11use nonempty::NonEmpty;
12use semver::{Comparator, Error, Op, Version, VersionReq};
13use serde::{de, Deserialize, Deserializer, Serialize};
14use thiserror::Error;
15#[derive(Debug, Error, Diagnostic)]
16pub enum VersionReqToVersionError {
17    #[error("cannot parse version from non-exact version requirement '{0}'")]
18    NonExactVersionReq(VersionReq),
19    #[error("cannot parse version from version requirement '*' (any version)")]
20    Any,
21}
22
23#[derive(Clone, Eq, PartialEq, Hash, Debug)]
24pub enum PackageVersion {
25    /// **SemVer version** as defined by <https://semver.org>,
26    /// but a bit more lenient for compatibility with luarocks
27    SemVer(SemVer),
28    /// A known **Dev** version
29    DevVer(DevVer),
30    /// An arbitrary string version.
31    /// Yes, luarocks-site allows arbitrary string versions in the root manifest
32    /// ┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
33    StringVer(StringVer),
34}
35
36impl HasModRev for PackageVersion {
37    fn to_modrev_string(&self) -> String {
38        match self {
39            Self::SemVer(ver) => ver.to_modrev_string(),
40            Self::DevVer(ver) => ver.to_modrev_string(),
41            Self::StringVer(ver) => ver.to_modrev_string(),
42        }
43    }
44}
45
46impl PackageVersion {
47    pub fn parse(text: &str) -> Result<Self, PackageVersionParseError> {
48        PackageVersion::from_str(text)
49    }
50    /// Note that this loses the specrev information.
51    pub fn into_version_req(&self) -> PackageVersionReq {
52        match self {
53            PackageVersion::DevVer(DevVer { modrev, .. }) => {
54                PackageVersionReq::DevVer(modrev.to_owned())
55            }
56            PackageVersion::StringVer(StringVer { modrev, .. }) => {
57                PackageVersionReq::StringVer(modrev.to_owned())
58            }
59            PackageVersion::SemVer(SemVer { version, .. }) => {
60                let version = version.to_owned();
61                PackageVersionReq::SemVer(VersionReq {
62                    comparators: vec![Comparator {
63                        op: Op::Exact,
64                        major: version.major,
65                        minor: Some(version.minor),
66                        patch: Some(version.patch),
67                        pre: version.pre,
68                    }],
69                })
70            }
71        }
72    }
73
74    pub(crate) fn is_semver(&self) -> bool {
75        matches!(self, PackageVersion::SemVer(_))
76    }
77
78    pub(crate) fn default_dev_version() -> Self {
79        Self::DevVer(DevVer::default())
80    }
81
82    pub(crate) fn default_dev_version_with_specrev(specrev: SpecRev) -> Self {
83        Self::DevVer(DevVer::default_with_specrev(specrev))
84    }
85}
86
87impl TryFrom<PackageVersionReq> for PackageVersion {
88    type Error = VersionReqToVersionError;
89
90    fn try_from(req: PackageVersionReq) -> Result<Self, Self::Error> {
91        match req {
92            PackageVersionReq::SemVer(version_req) => {
93                match NonEmpty::try_from(version_req.comparators.clone()) {
94                    Ok(comparators)
95                        if comparators
96                            .iter()
97                            .any(|comparator| comparator.op != semver::Op::Exact) =>
98                    {
99                        Err(VersionReqToVersionError::NonExactVersionReq(version_req))
100                    }
101                    Ok(comparators) => {
102                        let comparator = comparators.first();
103                        let version = semver::Version {
104                            major: comparator.major,
105                            minor: comparator.minor.unwrap_or(0),
106                            patch: comparator.patch.unwrap_or(0),
107                            pre: comparator.pre.clone(),
108                            build: semver::BuildMetadata::EMPTY,
109                        };
110                        let component_count = if comparator.patch.is_some() {
111                            3
112                        } else if comparator.minor.is_some() {
113                            2
114                        } else {
115                            1
116                        };
117                        Ok(PackageVersion::SemVer(SemVer {
118                            version,
119                            component_count,
120                            specrev: 1.into(),
121                        }))
122                    }
123                    Err(_) => Err(VersionReqToVersionError::NonExactVersionReq(version_req)),
124                }
125            }
126            PackageVersionReq::DevVer(modrev) => Ok(PackageVersion::DevVer(DevVer {
127                modrev,
128                specrev: 1.into(),
129            })),
130            PackageVersionReq::StringVer(modrev) => Ok(PackageVersion::StringVer(StringVer {
131                modrev,
132                specrev: 1.into(),
133            })),
134            PackageVersionReq::Any => Err(VersionReqToVersionError::Any),
135        }
136    }
137}
138
139#[derive(Error, Debug, Diagnostic)]
140#[non_exhaustive]
141pub enum PackageVersionParseError {
142    #[error(transparent)]
143    #[diagnostic(transparent)]
144    Specrev(#[from] SpecrevParseError),
145    #[error("failed to parse version: {0}")]
146    Version(#[from] Error),
147}
148
149impl Serialize for PackageVersion {
150    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
151    where
152        S: serde::Serializer,
153    {
154        match self {
155            PackageVersion::SemVer(version) => version.serialize(serializer),
156            PackageVersion::DevVer(version) => version.serialize(serializer),
157            PackageVersion::StringVer(version) => version.serialize(serializer),
158        }
159    }
160}
161
162impl<'de> Deserialize<'de> for PackageVersion {
163    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
164    where
165        D: Deserializer<'de>,
166    {
167        let s = String::deserialize(deserializer)?;
168        Self::from_str(&s).map_err(de::Error::custom)
169    }
170}
171
172impl Display for PackageVersion {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        match self {
175            PackageVersion::SemVer(version) => version.fmt(f),
176            PackageVersion::DevVer(version) => version.fmt(f),
177            PackageVersion::StringVer(version) => version.fmt(f),
178        }
179    }
180}
181
182impl PartialOrd for PackageVersion {
183    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
184        Some(self.cmp(other))
185    }
186}
187
188impl Ord for PackageVersion {
189    fn cmp(&self, other: &Self) -> Ordering {
190        match (self, other) {
191            (PackageVersion::SemVer(a), PackageVersion::SemVer(b)) => a.cmp(b),
192            (PackageVersion::SemVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
193            (PackageVersion::SemVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
194            (PackageVersion::DevVer(..), PackageVersion::SemVer(..)) => Ordering::Greater,
195            (PackageVersion::DevVer(a), PackageVersion::DevVer(b)) => a.cmp(b),
196            (PackageVersion::DevVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
197            (PackageVersion::StringVer(a), PackageVersion::StringVer(b)) => a.cmp(b),
198            (PackageVersion::StringVer(..), PackageVersion::SemVer(..)) => Ordering::Less,
199            (PackageVersion::StringVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
200        }
201    }
202}
203
204impl FromStr for PackageVersion {
205    type Err = PackageVersionParseError;
206
207    fn from_str(text: &str) -> Result<Self, Self::Err> {
208        let (modrev, specrev) = split_specrev(text)?;
209        match modrev {
210            "scm" => Ok(PackageVersion::DevVer(DevVer {
211                modrev: DevVersion::Scm,
212                specrev,
213            })),
214            "dev" => Ok(PackageVersion::DevVer(DevVer {
215                modrev: DevVersion::Dev,
216                specrev,
217            })),
218            modrev => match parse_version(modrev) {
219                Ok(version) => Ok(PackageVersion::SemVer(SemVer {
220                    component_count: cmp::min(text.chars().filter(|c| *c == '.').count() + 1, 3),
221                    version,
222                    specrev,
223                })),
224                Err(_) => Ok(PackageVersion::StringVer(StringVer {
225                    modrev: modrev.into(),
226                    specrev,
227                })),
228            },
229        }
230    }
231}
232
233/// The revision of a rov
234#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
235pub struct SpecRev(u16);
236
237impl From<u16> for SpecRev {
238    fn from(value: u16) -> Self {
239        Self(value)
240    }
241}
242
243impl Display for SpecRev {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        self.0.fmt(f)
246    }
247}
248
249impl Default for SpecRev {
250    fn default() -> Self {
251        Self(1)
252    }
253}
254
255/// Iterates `SpecRev`s upwards, starting from `1`.
256pub(crate) struct SpecRevIterator {
257    current: u16,
258}
259
260impl SpecRevIterator {
261    pub fn new() -> Self {
262        SpecRevIterator {
263            current: SpecRev::default().0,
264        }
265    }
266}
267
268impl Iterator for SpecRevIterator {
269    type Item = SpecRev;
270
271    fn next(&mut self) -> Option<Self::Item> {
272        let current = self.current;
273        self.current += 1;
274        if self.current == 0 {
275            None // overflow
276        } else {
277            Some(SpecRev(current))
278        }
279    }
280}
281
282// TODO: Stop deriving Eq here
283#[derive(Clone, Eq, PartialEq, Hash, Debug)]
284pub struct SemVer {
285    version: Version,
286    component_count: usize,
287    specrev: SpecRev,
288}
289
290impl HasModRev for SemVer {
291    fn to_modrev_string(&self) -> String {
292        self.version.to_string()
293    }
294}
295
296impl Display for SemVer {
297    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298        let (version_str, remainder) = split_semver_version(&self.version.to_string());
299        let mut luarocks_version_str = version_str.split('.').take(self.component_count).join(".");
300        if let Some(remainder) = remainder {
301            // luarocks allows and arbitrary number of '.' separators
302            // We treat anything after the third '.' as a semver prerelease/build version,
303            // so we have to convert it back for luarocks.
304            luarocks_version_str.push_str(&format!(".{remainder}"));
305        }
306        let str = format!("{}-{}", luarocks_version_str, self.specrev);
307        str.fmt(f)
308    }
309}
310
311fn split_semver_version(version_str: &str) -> (String, Option<String>) {
312    if let Some(pos) = version_str.rfind('-') {
313        if let Some(pre_build_str) = version_str.get(pos + 1..) {
314            (version_str[..pos].into(), Some(pre_build_str.into()))
315        } else {
316            (version_str[..pos].into(), None)
317        }
318    } else {
319        (version_str.into(), None)
320    }
321}
322
323impl Serialize for SemVer {
324    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
325    where
326        S: serde::Serializer,
327    {
328        self.to_string().serialize(serializer)
329    }
330}
331
332impl Ord for SemVer {
333    fn cmp(&self, other: &Self) -> Ordering {
334        let result = self.version.cmp(&other.version);
335        if result == Ordering::Equal {
336            return self.specrev.cmp(&other.specrev);
337        }
338        result
339    }
340}
341
342impl PartialOrd for SemVer {
343    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
344        Some(self.cmp(other))
345    }
346}
347
348#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default)]
349#[serde(rename_all = "lowercase")]
350pub enum DevVersion {
351    #[default]
352    Dev,
353    Scm,
354}
355
356impl Display for DevVersion {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        match self {
359            Self::Dev => "dev".fmt(f),
360            Self::Scm => "scm".fmt(f),
361        }
362    }
363}
364
365#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
366pub struct DevVer {
367    modrev: DevVersion,
368    specrev: SpecRev,
369}
370
371impl DevVer {
372    fn default_with_specrev(specrev: SpecRev) -> Self {
373        Self {
374            modrev: DevVersion::default(),
375            specrev,
376        }
377    }
378}
379
380impl HasModRev for DevVer {
381    fn to_modrev_string(&self) -> String {
382        self.modrev.to_string().to_lowercase()
383    }
384}
385
386impl Display for DevVer {
387    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388        let str = format!("{}-{}", self.modrev, self.specrev);
389        str.fmt(f)
390    }
391}
392
393impl Serialize for DevVer {
394    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
395    where
396        S: serde::Serializer,
397    {
398        self.to_string().serialize(serializer)
399    }
400}
401
402impl Ord for DevVer {
403    fn cmp(&self, other: &Self) -> Ordering {
404        // NOTE: We compare specrevs first for dev versions
405        let result = self.specrev.cmp(&other.specrev);
406        if result == Ordering::Equal {
407            return self.modrev.cmp(&other.modrev);
408        }
409        result
410    }
411}
412
413impl PartialOrd for DevVer {
414    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
415        Some(self.cmp(other))
416    }
417}
418
419#[derive(Clone, Eq, PartialEq, Hash, Debug)]
420pub struct StringVer {
421    modrev: String,
422    specrev: SpecRev,
423}
424
425impl HasModRev for StringVer {
426    fn to_modrev_string(&self) -> String {
427        self.modrev.to_string()
428    }
429}
430
431impl Display for StringVer {
432    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433        let str = format!("{}-{}", self.modrev, self.specrev);
434        str.fmt(f)
435    }
436}
437
438impl Serialize for StringVer {
439    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
440    where
441        S: serde::Serializer,
442    {
443        self.to_string().serialize(serializer)
444    }
445}
446
447impl Ord for StringVer {
448    fn cmp(&self, other: &Self) -> Ordering {
449        // NOTE: We compare specrevs first for dev versions
450        let result = self.specrev.cmp(&other.specrev);
451        if result == Ordering::Equal {
452            return self.modrev.cmp(&other.modrev);
453        }
454        result
455    }
456}
457
458impl PartialOrd for StringVer {
459    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
460        Some(self.cmp(other))
461    }
462}
463
464pub(crate) trait HasModRev {
465    /// If a version has a modrev (and possibly a specrev),
466    /// this is equivalent to `to_string()`, but includes only the modrev.
467    fn to_modrev_string(&self) -> String;
468}
469
470#[derive(Error, Debug, Diagnostic)]
471#[non_exhaustive]
472#[error(transparent)]
473pub struct PackageVersionReqError(#[from] Error);
474
475/// **SemVer version** requirement as defined by <https://semver.org>.
476/// or a **Dev** version requirement, which can be one of "dev", "scm", or "git"
477#[derive(Clone, Eq, PartialEq, Hash, Debug)]
478pub enum PackageVersionReq {
479    /// A PackageVersionReq that matches a SemVer version.
480    SemVer(VersionReq),
481    /// A PackageVersionReq that matches only known dev versions.
482    DevVer(DevVersion),
483    /// A PackageVersionReq that matches a arbitrary string version.
484    StringVer(String),
485    /// A PackageVersionReq that has no version constraint.
486    Any,
487}
488
489impl PackageVersionReq {
490    /// Returns a `PackageVersionReq` that matches any version.
491    pub fn any() -> Self {
492        PackageVersionReq::Any
493    }
494
495    pub fn parse(text: &str) -> Result<Self, PackageVersionReqError> {
496        PackageVersionReq::from_str(text)
497    }
498
499    pub fn matches(&self, version: &PackageVersion) -> bool {
500        match (self, version) {
501            (PackageVersionReq::SemVer(req), PackageVersion::SemVer(ver)) => {
502                req.matches(&ver.version)
503            }
504            (PackageVersionReq::DevVer(req), PackageVersion::DevVer(ver)) => req == &ver.modrev,
505            (PackageVersionReq::StringVer(req), PackageVersion::StringVer(ver)) => {
506                req == &ver.modrev
507            }
508            (PackageVersionReq::Any, _) => true,
509            _ => false,
510        }
511    }
512
513    pub fn is_any(&self) -> bool {
514        matches!(self, PackageVersionReq::Any)
515    }
516}
517
518impl Display for PackageVersionReq {
519    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520        match self {
521            PackageVersionReq::SemVer(version_req) => {
522                let mut str = version_req.to_string();
523                if str.starts_with("=") {
524                    str = str.replacen("=", "==", 1);
525                } else if str.starts_with("^") {
526                    str = str.replacen("^", "~>", 1);
527                }
528                str.fmt(f)
529            }
530            PackageVersionReq::DevVer(name_req) => write!(f, "=={}", &name_req),
531            PackageVersionReq::StringVer(name_req) => write!(f, "=={}", &name_req),
532            PackageVersionReq::Any => f.write_str("any"),
533        }
534    }
535}
536
537impl<'de> Deserialize<'de> for PackageVersionReq {
538    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
539    where
540        D: Deserializer<'de>,
541    {
542        String::deserialize(deserializer)?
543            .parse()
544            .map_err(serde::de::Error::custom)
545    }
546}
547
548impl FromStr for PackageVersionReq {
549    type Err = PackageVersionReqError;
550
551    fn from_str(text: &str) -> Result<Self, Self::Err> {
552        let text = correct_version_req_str(text);
553
554        let trimmed = text.trim_start_matches('=').trim_start_matches('@').trim();
555
556        match parse_version_req(&text) {
557            Ok(_) => Ok(PackageVersionReq::SemVer(parse_version_req(&text)?)),
558            Err(_) => match trimmed {
559                "scm" => Ok(PackageVersionReq::DevVer(DevVersion::Scm)),
560                "dev" => Ok(PackageVersionReq::DevVer(DevVersion::Dev)),
561                ver => Ok(PackageVersionReq::StringVer(ver.to_string())),
562            },
563        }
564    }
565}
566
567fn correct_version_req_str(text: &str) -> String {
568    text.chars()
569        .chunk_by(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
570        .into_iter()
571        .map(|(is_version_str, chars)| (is_version_str, chars.collect::<String>()))
572        .map(|(is_version_str, chunk)| {
573            if is_version_str && !is_known_dev_version_str(&chunk) {
574                let version_str = trim_specrev(&chunk);
575                correct_prerelease_version_string(version_str)
576            } else {
577                chunk
578            }
579        })
580        .collect::<String>()
581}
582
583fn trim_specrev(version_str: &str) -> &str {
584    if let Some(pos) = version_str.rfind('-') {
585        &version_str[..pos]
586    } else {
587        version_str
588    }
589}
590
591#[derive(Error, Debug, Diagnostic)]
592#[non_exhaustive]
593pub enum SpecrevParseError {
594    #[error("specrev {specrev} in version {full_version} contains non-numeric characters")]
595    InvalidSpecrev {
596        specrev: String,
597        full_version: String,
598    },
599    #[error("could not parse specrev in version {0}")]
600    InvalidVersion(String),
601}
602
603fn split_specrev(version_str: &str) -> Result<(&str, SpecRev), SpecrevParseError> {
604    if let Some(pos) = version_str.rfind('-') {
605        if let Some(specrev_str) = version_str.get(pos + 1..) {
606            if specrev_str.chars().all(|c| c.is_ascii_digit()) {
607                let specrev =
608                    specrev_str
609                        .parse::<u16>()
610                        .map_err(|_| SpecrevParseError::InvalidSpecrev {
611                            specrev: specrev_str.into(),
612                            full_version: version_str.into(),
613                        })?;
614                Ok((&version_str[..pos], specrev.into()))
615            } else {
616                Err(SpecrevParseError::InvalidSpecrev {
617                    specrev: specrev_str.into(),
618                    full_version: version_str.into(),
619                })
620            }
621        } else {
622            Err(SpecrevParseError::InvalidVersion(version_str.into()))
623        }
624    } else {
625        // We assume a specrev of 1 if none can be found.
626        Ok((version_str, 1.into()))
627    }
628}
629
630fn is_known_dev_version_str(text: &str) -> bool {
631    matches!(text, "dev" | "scm")
632}
633
634/// Parses a Version from a string, automatically supplying any missing details (i.e. missing
635/// minor/patch sections).
636fn parse_version(s: &str) -> Result<Version, Error> {
637    let version_str = correct_version_string(s);
638    Version::parse(&version_str)
639}
640
641/// Transform LuaRocks constraints into constraints that can be parsed by the semver crate.
642fn parse_version_req(version_constraints: &str) -> Result<VersionReq, Error> {
643    let unescaped = decode_html_entities(version_constraints)
644        .to_string()
645        .as_str()
646        .to_owned();
647    let transformed = match unescaped {
648        s if s.starts_with("~>") => parse_pessimistic_version_constraint(s)?,
649        s if s.starts_with("@") => format!("={}", &s[1..]),
650        // The semver crate only understands "= version", unlike luarocks which understands "== version".
651        s if s.starts_with("==") => s[1..].to_string(),
652        s if s // semver parses no constraint prefix as ^ (equivalent to ~>)
653            .find(|c: char| c.is_alphanumeric())
654            .is_some_and(|idx| idx == 0) =>
655        {
656            format!("={}", &s)
657        }
658        s => s,
659    };
660
661    let version_req = VersionReq::parse(&transformed)?;
662    Ok(version_req)
663}
664
665fn parse_pessimistic_version_constraint(version_constraint: String) -> Result<String, Error> {
666    // pessimistic operator
667    let min_version_str = &version_constraint[2..].trim();
668    let min_version = Version::parse(&correct_version_string(min_version_str))?;
669
670    let max_version = match min_version_str.matches('.').count() {
671        0 => Version {
672            major: &min_version.major + 1,
673            ..min_version.clone()
674        },
675        1 => Version {
676            minor: &min_version.minor + 1,
677            ..min_version.clone()
678        },
679        _ => Version {
680            patch: &min_version.patch + 1,
681            ..min_version.clone()
682        },
683    };
684
685    Ok(format!(">= {min_version}, < {max_version}"))
686}
687
688/// ┻━┻ ︵╰(°□°╰) Luarocks allows for an arbitrary number of version digits
689/// This function attempts to correct a non-semver compliant version string,
690/// by swapping the third '.' out with a '-', converting the non-semver
691/// compliant digits to a pre-release identifier.
692fn correct_version_string(version: &str) -> String {
693    let version = append_minor_patch_if_missing(version);
694    correct_prerelease_version_string(&version)
695}
696
697fn correct_prerelease_version_string(version: &str) -> String {
698    let parts: Vec<&str> = version.split('.').collect();
699    if parts.len() > 3 {
700        let corrected_version = format!(
701            "{}.{}.{}-{}",
702            parts[0],
703            parts[1],
704            parts[2],
705            parts[3..].join(".")
706        );
707        corrected_version
708    } else {
709        version.to_string()
710    }
711}
712
713/// Recursively append .0 until the version string has a minor or patch version
714fn append_minor_patch_if_missing(version: &str) -> String {
715    if version.matches('.').count() < 2 {
716        append_minor_patch_if_missing(&format!("{version}.0"))
717    } else {
718        version.to_string()
719    }
720}
721
722#[cfg(test)]
723mod tests {
724    use super::*;
725
726    #[tokio::test]
727    async fn parse_semver_version() {
728        assert_eq!(
729            PackageVersion::parse("1-1").unwrap(),
730            PackageVersion::SemVer(SemVer {
731                version: "1.0.0".parse().unwrap(),
732                component_count: 1,
733                specrev: 1.into(),
734            })
735        );
736        assert_eq!(
737            PackageVersion::parse("1.0-1").unwrap(),
738            PackageVersion::SemVer(SemVer {
739                version: "1.0.0".parse().unwrap(),
740                component_count: 2,
741                specrev: 1.into(),
742            })
743        );
744        assert_eq!(
745            PackageVersion::parse("1.0.0-1").unwrap(),
746            PackageVersion::SemVer(SemVer {
747                version: "1.0.0".parse().unwrap(),
748                component_count: 3,
749                specrev: 1.into()
750            })
751        );
752        assert_eq!(
753            PackageVersion::parse("1.0.0-1").unwrap(),
754            PackageVersion::SemVer(SemVer {
755                version: "1.0.0".parse().unwrap(),
756                component_count: 3,
757                specrev: 1.into()
758            })
759        );
760        assert_eq!(
761            PackageVersion::parse("1.0.0-10-1").unwrap(),
762            PackageVersion::SemVer(SemVer {
763                version: "1.0.0-10".parse().unwrap(),
764                component_count: 3,
765                specrev: 1.into()
766            })
767        );
768        assert_eq!(
769            PackageVersion::parse("1.0.0.10-1").unwrap(),
770            PackageVersion::SemVer(SemVer {
771                version: "1.0.0-10".parse().unwrap(),
772                component_count: 3,
773                specrev: 1.into()
774            })
775        );
776        assert_eq!(
777            PackageVersion::parse("1.0.0.10.0-1").unwrap(),
778            PackageVersion::SemVer(SemVer {
779                version: "1.0.0-10.0".parse().unwrap(),
780                component_count: 3,
781                specrev: 1.into()
782            })
783        );
784    }
785
786    #[tokio::test]
787    async fn parse_dev_version() {
788        assert_eq!(
789            PackageVersion::parse("dev-1").unwrap(),
790            PackageVersion::DevVer(DevVer {
791                modrev: DevVersion::Dev,
792                specrev: 1.into()
793            })
794        );
795        assert_eq!(
796            PackageVersion::parse("scm-1").unwrap(),
797            PackageVersion::DevVer(DevVer {
798                modrev: DevVersion::Scm,
799                specrev: 1.into()
800            })
801        );
802        assert_eq!(
803            PackageVersion::parse("git-1").unwrap(),
804            PackageVersion::StringVer(StringVer {
805                modrev: "git".into(),
806                specrev: 1.into()
807            })
808        );
809        assert_eq!(
810            PackageVersion::parse("scm-1").unwrap(),
811            PackageVersion::DevVer(DevVer {
812                modrev: DevVersion::Scm,
813                specrev: 1.into()
814            })
815        );
816    }
817
818    #[tokio::test]
819    async fn parse_dev_version_req() {
820        assert_eq!(
821            PackageVersionReq::parse("dev").unwrap(),
822            PackageVersionReq::DevVer(DevVersion::Dev)
823        );
824        assert_eq!(
825            PackageVersionReq::parse("scm").unwrap(),
826            PackageVersionReq::DevVer(DevVersion::Scm)
827        );
828        assert_eq!(
829            PackageVersionReq::parse("git").unwrap(),
830            PackageVersionReq::StringVer("git".into())
831        );
832        assert_eq!(
833            PackageVersionReq::parse("==dev").unwrap(),
834            PackageVersionReq::DevVer(DevVersion::Dev)
835        );
836        assert_eq!(
837            PackageVersionReq::parse("==git").unwrap(),
838            PackageVersionReq::StringVer("git".into())
839        );
840        assert_eq!(
841            PackageVersionReq::parse("== dev").unwrap(),
842            PackageVersionReq::DevVer(DevVersion::Dev)
843        );
844        assert_eq!(
845            PackageVersionReq::parse("== scm").unwrap(),
846            PackageVersionReq::DevVer(DevVersion::Scm)
847        );
848        assert_eq!(
849            PackageVersionReq::parse("@dev").unwrap(),
850            PackageVersionReq::DevVer(DevVersion::Dev)
851        );
852        assert_eq!(
853            PackageVersionReq::parse("@git").unwrap(),
854            PackageVersionReq::StringVer("git".into())
855        );
856        assert_eq!(
857            PackageVersionReq::parse("@ dev").unwrap(),
858            PackageVersionReq::DevVer(DevVersion::Dev)
859        );
860        assert_eq!(
861            PackageVersionReq::parse("@ scm").unwrap(),
862            PackageVersionReq::DevVer(DevVersion::Scm)
863        );
864        assert_eq!(
865            PackageVersionReq::parse(">1-1,<1.2-2").unwrap(),
866            PackageVersionReq::SemVer(">1,<1.2".parse().unwrap())
867        );
868        assert_eq!(
869            PackageVersionReq::parse("> 1-1, < 1.2-2").unwrap(),
870            PackageVersionReq::SemVer("> 1, < 1.2".parse().unwrap())
871        );
872        assert_eq!(
873            PackageVersionReq::parse("> 2.1.0.10, < 2.1.1").unwrap(),
874            PackageVersionReq::SemVer("> 2.1.0-10, < 2.1.1".parse().unwrap())
875        );
876    }
877
878    #[tokio::test]
879    async fn package_version_req_semver_roundtrips() {
880        let req = PackageVersionReq::parse("==0.7.1").unwrap();
881        assert_eq!(req.to_string(), "==0.7.1");
882
883        let req = PackageVersionReq::parse("0.7.1").unwrap();
884        assert_eq!(req.to_string(), "==0.7.1");
885
886        let req = PackageVersionReq::parse(">=0.7.1").unwrap();
887        assert_eq!(req.to_string(), ">=0.7.1");
888
889        let req = PackageVersionReq::parse(">0.7.1").unwrap();
890        assert_eq!(req.to_string(), ">0.7.1");
891
892        let req = PackageVersionReq::parse("<0.7.1").unwrap();
893        assert_eq!(req.to_string(), "<0.7.1");
894
895        let req = PackageVersionReq::parse("~> 0.7.1").unwrap();
896        assert_eq!(req.to_string(), ">=0.7.1, <0.7.2");
897    }
898
899    #[tokio::test]
900    async fn package_version_req_devver_roundtrips() {
901        let req = PackageVersionReq::parse("==scm").unwrap();
902        assert_eq!(req.to_string(), "==scm");
903
904        let req = PackageVersionReq::parse("@scm").unwrap();
905        assert_eq!(req.to_string(), "==scm");
906
907        let req = PackageVersionReq::parse("scm").unwrap();
908        assert_eq!(req.to_string(), "==scm");
909
910        let req = PackageVersionReq::parse("==a144124839f027a2d0a95791936c478d047126fc").unwrap();
911        assert_eq!(
912            req.to_string(),
913            "==a144124839f027a2d0a95791936c478d047126fc"
914        );
915    }
916}