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(SemVer),
28 DevVer(DevVer),
30 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 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#[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
255pub(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 } else {
277 Some(SpecRev(current))
278 }
279 }
280}
281
282#[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_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 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 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 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#[derive(Clone, Eq, PartialEq, Hash, Debug)]
478pub enum PackageVersionReq {
479 SemVer(VersionReq),
481 DevVer(DevVersion),
483 StringVer(String),
485 Any,
487}
488
489impl PackageVersionReq {
490 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 Ok((version_str, 1.into()))
627 }
628}
629
630fn is_known_dev_version_str(text: &str) -> bool {
631 matches!(text, "dev" | "scm")
632}
633
634fn parse_version(s: &str) -> Result<Version, Error> {
637 let version_str = correct_version_string(s);
638 Version::parse(&version_str)
639}
640
641fn 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 s if s.starts_with("==") => s[1..].to_string(),
652 s if s .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 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
688fn 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
713fn 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}