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)]
140pub enum PackageVersionParseError {
141 #[error(transparent)]
142 #[diagnostic(transparent)]
143 Specrev(#[from] SpecrevParseError),
144 #[error("failed to parse version: {0}")]
145 Version(#[from] Error),
146}
147
148impl Serialize for PackageVersion {
149 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
150 where
151 S: serde::Serializer,
152 {
153 match self {
154 PackageVersion::SemVer(version) => version.serialize(serializer),
155 PackageVersion::DevVer(version) => version.serialize(serializer),
156 PackageVersion::StringVer(version) => version.serialize(serializer),
157 }
158 }
159}
160
161impl<'de> Deserialize<'de> for PackageVersion {
162 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163 where
164 D: Deserializer<'de>,
165 {
166 let s = String::deserialize(deserializer)?;
167 Self::from_str(&s).map_err(de::Error::custom)
168 }
169}
170
171impl Display for PackageVersion {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 match self {
174 PackageVersion::SemVer(version) => version.fmt(f),
175 PackageVersion::DevVer(version) => version.fmt(f),
176 PackageVersion::StringVer(version) => version.fmt(f),
177 }
178 }
179}
180
181impl PartialOrd for PackageVersion {
182 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
183 Some(self.cmp(other))
184 }
185}
186
187impl Ord for PackageVersion {
188 fn cmp(&self, other: &Self) -> Ordering {
189 match (self, other) {
190 (PackageVersion::SemVer(a), PackageVersion::SemVer(b)) => a.cmp(b),
191 (PackageVersion::SemVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
192 (PackageVersion::SemVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
193 (PackageVersion::DevVer(..), PackageVersion::SemVer(..)) => Ordering::Greater,
194 (PackageVersion::DevVer(a), PackageVersion::DevVer(b)) => a.cmp(b),
195 (PackageVersion::DevVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
196 (PackageVersion::StringVer(a), PackageVersion::StringVer(b)) => a.cmp(b),
197 (PackageVersion::StringVer(..), PackageVersion::SemVer(..)) => Ordering::Less,
198 (PackageVersion::StringVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
199 }
200 }
201}
202
203impl FromStr for PackageVersion {
204 type Err = PackageVersionParseError;
205
206 fn from_str(text: &str) -> Result<Self, Self::Err> {
207 let (modrev, specrev) = split_specrev(text)?;
208 match modrev {
209 "scm" => Ok(PackageVersion::DevVer(DevVer {
210 modrev: DevVersion::Scm,
211 specrev,
212 })),
213 "dev" => Ok(PackageVersion::DevVer(DevVer {
214 modrev: DevVersion::Dev,
215 specrev,
216 })),
217 modrev => match parse_version(modrev) {
218 Ok(version) => Ok(PackageVersion::SemVer(SemVer {
219 component_count: cmp::min(text.chars().filter(|c| *c == '.').count() + 1, 3),
220 version,
221 specrev,
222 })),
223 Err(_) => Ok(PackageVersion::StringVer(StringVer {
224 modrev: modrev.into(),
225 specrev,
226 })),
227 },
228 }
229 }
230}
231
232#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
234pub struct SpecRev(u16);
235
236impl From<u16> for SpecRev {
237 fn from(value: u16) -> Self {
238 Self(value)
239 }
240}
241
242impl Display for SpecRev {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 self.0.fmt(f)
245 }
246}
247
248impl Default for SpecRev {
249 fn default() -> Self {
250 Self(1)
251 }
252}
253
254pub(crate) struct SpecRevIterator {
256 current: u16,
257}
258
259impl SpecRevIterator {
260 pub fn new() -> Self {
261 SpecRevIterator {
262 current: SpecRev::default().0,
263 }
264 }
265}
266
267impl Iterator for SpecRevIterator {
268 type Item = SpecRev;
269
270 fn next(&mut self) -> Option<Self::Item> {
271 let current = self.current;
272 self.current += 1;
273 if self.current == 0 {
274 None } else {
276 Some(SpecRev(current))
277 }
278 }
279}
280
281#[derive(Clone, Eq, PartialEq, Hash, Debug)]
283pub struct SemVer {
284 version: Version,
285 component_count: usize,
286 specrev: SpecRev,
287}
288
289impl HasModRev for SemVer {
290 fn to_modrev_string(&self) -> String {
291 self.version.to_string()
292 }
293}
294
295impl Display for SemVer {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 let (version_str, remainder) = split_semver_version(&self.version.to_string());
298 let mut luarocks_version_str = version_str.split('.').take(self.component_count).join(".");
299 if let Some(remainder) = remainder {
300 luarocks_version_str.push_str(&format!(".{remainder}"));
304 }
305 let str = format!("{}-{}", luarocks_version_str, self.specrev);
306 str.fmt(f)
307 }
308}
309
310fn split_semver_version(version_str: &str) -> (String, Option<String>) {
311 if let Some(pos) = version_str.rfind('-') {
312 if let Some(pre_build_str) = version_str.get(pos + 1..) {
313 (version_str[..pos].into(), Some(pre_build_str.into()))
314 } else {
315 (version_str[..pos].into(), None)
316 }
317 } else {
318 (version_str.into(), None)
319 }
320}
321
322impl Serialize for SemVer {
323 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
324 where
325 S: serde::Serializer,
326 {
327 self.to_string().serialize(serializer)
328 }
329}
330
331impl Ord for SemVer {
332 fn cmp(&self, other: &Self) -> Ordering {
333 let result = self.version.cmp(&other.version);
334 if result == Ordering::Equal {
335 return self.specrev.cmp(&other.specrev);
336 }
337 result
338 }
339}
340
341impl PartialOrd for SemVer {
342 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
343 Some(self.cmp(other))
344 }
345}
346
347#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default)]
348#[serde(rename_all = "lowercase")]
349pub enum DevVersion {
350 #[default]
351 Dev,
352 Scm,
353}
354
355impl Display for DevVersion {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 match self {
358 Self::Dev => "dev".fmt(f),
359 Self::Scm => "scm".fmt(f),
360 }
361 }
362}
363
364#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
365pub struct DevVer {
366 modrev: DevVersion,
367 specrev: SpecRev,
368}
369
370impl DevVer {
371 fn default_with_specrev(specrev: SpecRev) -> Self {
372 Self {
373 modrev: DevVersion::default(),
374 specrev,
375 }
376 }
377}
378
379impl HasModRev for DevVer {
380 fn to_modrev_string(&self) -> String {
381 self.modrev.to_string().to_lowercase()
382 }
383}
384
385impl Display for DevVer {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 let str = format!("{}-{}", self.modrev, self.specrev);
388 str.fmt(f)
389 }
390}
391
392impl Serialize for DevVer {
393 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
394 where
395 S: serde::Serializer,
396 {
397 self.to_string().serialize(serializer)
398 }
399}
400
401impl Ord for DevVer {
402 fn cmp(&self, other: &Self) -> Ordering {
403 let result = self.specrev.cmp(&other.specrev);
405 if result == Ordering::Equal {
406 return self.modrev.cmp(&other.modrev);
407 }
408 result
409 }
410}
411
412impl PartialOrd for DevVer {
413 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
414 Some(self.cmp(other))
415 }
416}
417
418#[derive(Clone, Eq, PartialEq, Hash, Debug)]
419pub struct StringVer {
420 modrev: String,
421 specrev: SpecRev,
422}
423
424impl HasModRev for StringVer {
425 fn to_modrev_string(&self) -> String {
426 self.modrev.to_string()
427 }
428}
429
430impl Display for StringVer {
431 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432 let str = format!("{}-{}", self.modrev, self.specrev);
433 str.fmt(f)
434 }
435}
436
437impl Serialize for StringVer {
438 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
439 where
440 S: serde::Serializer,
441 {
442 self.to_string().serialize(serializer)
443 }
444}
445
446impl Ord for StringVer {
447 fn cmp(&self, other: &Self) -> Ordering {
448 let result = self.specrev.cmp(&other.specrev);
450 if result == Ordering::Equal {
451 return self.modrev.cmp(&other.modrev);
452 }
453 result
454 }
455}
456
457impl PartialOrd for StringVer {
458 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
459 Some(self.cmp(other))
460 }
461}
462
463pub(crate) trait HasModRev {
464 fn to_modrev_string(&self) -> String;
467}
468
469#[derive(Error, Debug, Diagnostic)]
470#[error(transparent)]
471pub struct PackageVersionReqError(#[from] Error);
472
473#[derive(Clone, Eq, PartialEq, Hash, Debug)]
476pub enum PackageVersionReq {
477 SemVer(VersionReq),
479 DevVer(DevVersion),
481 StringVer(String),
483 Any,
485}
486
487impl PackageVersionReq {
488 pub fn any() -> Self {
490 PackageVersionReq::Any
491 }
492
493 pub fn parse(text: &str) -> Result<Self, PackageVersionReqError> {
494 PackageVersionReq::from_str(text)
495 }
496
497 pub fn matches(&self, version: &PackageVersion) -> bool {
498 match (self, version) {
499 (PackageVersionReq::SemVer(req), PackageVersion::SemVer(ver)) => {
500 req.matches(&ver.version)
501 }
502 (PackageVersionReq::DevVer(req), PackageVersion::DevVer(ver)) => req == &ver.modrev,
503 (PackageVersionReq::StringVer(req), PackageVersion::StringVer(ver)) => {
504 req == &ver.modrev
505 }
506 (PackageVersionReq::Any, _) => true,
507 _ => false,
508 }
509 }
510
511 pub fn is_any(&self) -> bool {
512 matches!(self, PackageVersionReq::Any)
513 }
514}
515
516impl Display for PackageVersionReq {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 match self {
519 PackageVersionReq::SemVer(version_req) => {
520 let mut str = version_req.to_string();
521 if str.starts_with("=") {
522 str = str.replacen("=", "==", 1);
523 } else if str.starts_with("^") {
524 str = str.replacen("^", "~>", 1);
525 }
526 str.fmt(f)
527 }
528 PackageVersionReq::DevVer(name_req) => write!(f, "=={}", &name_req),
529 PackageVersionReq::StringVer(name_req) => write!(f, "=={}", &name_req),
530 PackageVersionReq::Any => f.write_str("any"),
531 }
532 }
533}
534
535impl<'de> Deserialize<'de> for PackageVersionReq {
536 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
537 where
538 D: Deserializer<'de>,
539 {
540 String::deserialize(deserializer)?
541 .parse()
542 .map_err(serde::de::Error::custom)
543 }
544}
545
546impl FromStr for PackageVersionReq {
547 type Err = PackageVersionReqError;
548
549 fn from_str(text: &str) -> Result<Self, Self::Err> {
550 let text = correct_version_req_str(text);
551
552 let trimmed = text.trim_start_matches('=').trim_start_matches('@').trim();
553
554 match parse_version_req(&text) {
555 Ok(_) => Ok(PackageVersionReq::SemVer(parse_version_req(&text)?)),
556 Err(_) => match trimmed {
557 "scm" => Ok(PackageVersionReq::DevVer(DevVersion::Scm)),
558 "dev" => Ok(PackageVersionReq::DevVer(DevVersion::Dev)),
559 ver => Ok(PackageVersionReq::StringVer(ver.to_string())),
560 },
561 }
562 }
563}
564
565fn correct_version_req_str(text: &str) -> String {
566 text.chars()
567 .chunk_by(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
568 .into_iter()
569 .map(|(is_version_str, chars)| (is_version_str, chars.collect::<String>()))
570 .map(|(is_version_str, chunk)| {
571 if is_version_str && !is_known_dev_version_str(&chunk) {
572 let version_str = trim_specrev(&chunk);
573 correct_prerelease_version_string(version_str)
574 } else {
575 chunk
576 }
577 })
578 .collect::<String>()
579}
580
581fn trim_specrev(version_str: &str) -> &str {
582 if let Some(pos) = version_str.rfind('-') {
583 &version_str[..pos]
584 } else {
585 version_str
586 }
587}
588
589#[derive(Error, Debug, Diagnostic)]
590pub enum SpecrevParseError {
591 #[error("specrev {specrev} in version {full_version} contains non-numeric characters")]
592 InvalidSpecrev {
593 specrev: String,
594 full_version: String,
595 },
596 #[error("could not parse specrev in version {0}")]
597 InvalidVersion(String),
598}
599
600fn split_specrev(version_str: &str) -> Result<(&str, SpecRev), SpecrevParseError> {
601 if let Some(pos) = version_str.rfind('-') {
602 if let Some(specrev_str) = version_str.get(pos + 1..) {
603 if specrev_str.chars().all(|c| c.is_ascii_digit()) {
604 let specrev =
605 specrev_str
606 .parse::<u16>()
607 .map_err(|_| SpecrevParseError::InvalidSpecrev {
608 specrev: specrev_str.into(),
609 full_version: version_str.into(),
610 })?;
611 Ok((&version_str[..pos], specrev.into()))
612 } else {
613 Err(SpecrevParseError::InvalidSpecrev {
614 specrev: specrev_str.into(),
615 full_version: version_str.into(),
616 })
617 }
618 } else {
619 Err(SpecrevParseError::InvalidVersion(version_str.into()))
620 }
621 } else {
622 Ok((version_str, 1.into()))
624 }
625}
626
627fn is_known_dev_version_str(text: &str) -> bool {
628 matches!(text, "dev" | "scm")
629}
630
631fn parse_version(s: &str) -> Result<Version, Error> {
634 let version_str = correct_version_string(s);
635 Version::parse(&version_str)
636}
637
638fn parse_version_req(version_constraints: &str) -> Result<VersionReq, Error> {
640 let unescaped = decode_html_entities(version_constraints)
641 .to_string()
642 .as_str()
643 .to_owned();
644 let transformed = match unescaped {
645 s if s.starts_with("~>") => parse_pessimistic_version_constraint(s)?,
646 s if s.starts_with("@") => format!("={}", &s[1..]),
647 s if s.starts_with("==") => s[1..].to_string(),
649 s if s .find(|c: char| c.is_alphanumeric())
651 .is_some_and(|idx| idx == 0) =>
652 {
653 format!("={}", &s)
654 }
655 s => s,
656 };
657
658 let version_req = VersionReq::parse(&transformed)?;
659 Ok(version_req)
660}
661
662fn parse_pessimistic_version_constraint(version_constraint: String) -> Result<String, Error> {
663 let min_version_str = &version_constraint[2..].trim();
665 let min_version = Version::parse(&correct_version_string(min_version_str))?;
666
667 let max_version = match min_version_str.matches('.').count() {
668 0 => Version {
669 major: &min_version.major + 1,
670 ..min_version.clone()
671 },
672 1 => Version {
673 minor: &min_version.minor + 1,
674 ..min_version.clone()
675 },
676 _ => Version {
677 patch: &min_version.patch + 1,
678 ..min_version.clone()
679 },
680 };
681
682 Ok(format!(">= {min_version}, < {max_version}"))
683}
684
685fn correct_version_string(version: &str) -> String {
690 let version = append_minor_patch_if_missing(version);
691 correct_prerelease_version_string(&version)
692}
693
694fn correct_prerelease_version_string(version: &str) -> String {
695 let parts: Vec<&str> = version.split('.').collect();
696 if parts.len() > 3 {
697 let corrected_version = format!(
698 "{}.{}.{}-{}",
699 parts[0],
700 parts[1],
701 parts[2],
702 parts[3..].join(".")
703 );
704 corrected_version
705 } else {
706 version.to_string()
707 }
708}
709
710fn append_minor_patch_if_missing(version: &str) -> String {
712 if version.matches('.').count() < 2 {
713 append_minor_patch_if_missing(&format!("{version}.0"))
714 } else {
715 version.to_string()
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722
723 #[tokio::test]
724 async fn parse_semver_version() {
725 assert_eq!(
726 PackageVersion::parse("1-1").unwrap(),
727 PackageVersion::SemVer(SemVer {
728 version: "1.0.0".parse().unwrap(),
729 component_count: 1,
730 specrev: 1.into(),
731 })
732 );
733 assert_eq!(
734 PackageVersion::parse("1.0-1").unwrap(),
735 PackageVersion::SemVer(SemVer {
736 version: "1.0.0".parse().unwrap(),
737 component_count: 2,
738 specrev: 1.into(),
739 })
740 );
741 assert_eq!(
742 PackageVersion::parse("1.0.0-1").unwrap(),
743 PackageVersion::SemVer(SemVer {
744 version: "1.0.0".parse().unwrap(),
745 component_count: 3,
746 specrev: 1.into()
747 })
748 );
749 assert_eq!(
750 PackageVersion::parse("1.0.0-1").unwrap(),
751 PackageVersion::SemVer(SemVer {
752 version: "1.0.0".parse().unwrap(),
753 component_count: 3,
754 specrev: 1.into()
755 })
756 );
757 assert_eq!(
758 PackageVersion::parse("1.0.0-10-1").unwrap(),
759 PackageVersion::SemVer(SemVer {
760 version: "1.0.0-10".parse().unwrap(),
761 component_count: 3,
762 specrev: 1.into()
763 })
764 );
765 assert_eq!(
766 PackageVersion::parse("1.0.0.10-1").unwrap(),
767 PackageVersion::SemVer(SemVer {
768 version: "1.0.0-10".parse().unwrap(),
769 component_count: 3,
770 specrev: 1.into()
771 })
772 );
773 assert_eq!(
774 PackageVersion::parse("1.0.0.10.0-1").unwrap(),
775 PackageVersion::SemVer(SemVer {
776 version: "1.0.0-10.0".parse().unwrap(),
777 component_count: 3,
778 specrev: 1.into()
779 })
780 );
781 }
782
783 #[tokio::test]
784 async fn parse_dev_version() {
785 assert_eq!(
786 PackageVersion::parse("dev-1").unwrap(),
787 PackageVersion::DevVer(DevVer {
788 modrev: DevVersion::Dev,
789 specrev: 1.into()
790 })
791 );
792 assert_eq!(
793 PackageVersion::parse("scm-1").unwrap(),
794 PackageVersion::DevVer(DevVer {
795 modrev: DevVersion::Scm,
796 specrev: 1.into()
797 })
798 );
799 assert_eq!(
800 PackageVersion::parse("git-1").unwrap(),
801 PackageVersion::StringVer(StringVer {
802 modrev: "git".into(),
803 specrev: 1.into()
804 })
805 );
806 assert_eq!(
807 PackageVersion::parse("scm-1").unwrap(),
808 PackageVersion::DevVer(DevVer {
809 modrev: DevVersion::Scm,
810 specrev: 1.into()
811 })
812 );
813 }
814
815 #[tokio::test]
816 async fn parse_dev_version_req() {
817 assert_eq!(
818 PackageVersionReq::parse("dev").unwrap(),
819 PackageVersionReq::DevVer(DevVersion::Dev)
820 );
821 assert_eq!(
822 PackageVersionReq::parse("scm").unwrap(),
823 PackageVersionReq::DevVer(DevVersion::Scm)
824 );
825 assert_eq!(
826 PackageVersionReq::parse("git").unwrap(),
827 PackageVersionReq::StringVer("git".into())
828 );
829 assert_eq!(
830 PackageVersionReq::parse("==dev").unwrap(),
831 PackageVersionReq::DevVer(DevVersion::Dev)
832 );
833 assert_eq!(
834 PackageVersionReq::parse("==git").unwrap(),
835 PackageVersionReq::StringVer("git".into())
836 );
837 assert_eq!(
838 PackageVersionReq::parse("== dev").unwrap(),
839 PackageVersionReq::DevVer(DevVersion::Dev)
840 );
841 assert_eq!(
842 PackageVersionReq::parse("== scm").unwrap(),
843 PackageVersionReq::DevVer(DevVersion::Scm)
844 );
845 assert_eq!(
846 PackageVersionReq::parse("@dev").unwrap(),
847 PackageVersionReq::DevVer(DevVersion::Dev)
848 );
849 assert_eq!(
850 PackageVersionReq::parse("@git").unwrap(),
851 PackageVersionReq::StringVer("git".into())
852 );
853 assert_eq!(
854 PackageVersionReq::parse("@ dev").unwrap(),
855 PackageVersionReq::DevVer(DevVersion::Dev)
856 );
857 assert_eq!(
858 PackageVersionReq::parse("@ scm").unwrap(),
859 PackageVersionReq::DevVer(DevVersion::Scm)
860 );
861 assert_eq!(
862 PackageVersionReq::parse(">1-1,<1.2-2").unwrap(),
863 PackageVersionReq::SemVer(">1,<1.2".parse().unwrap())
864 );
865 assert_eq!(
866 PackageVersionReq::parse("> 1-1, < 1.2-2").unwrap(),
867 PackageVersionReq::SemVer("> 1, < 1.2".parse().unwrap())
868 );
869 assert_eq!(
870 PackageVersionReq::parse("> 2.1.0.10, < 2.1.1").unwrap(),
871 PackageVersionReq::SemVer("> 2.1.0-10, < 2.1.1".parse().unwrap())
872 );
873 }
874
875 #[tokio::test]
876 async fn package_version_req_semver_roundtrips() {
877 let req = PackageVersionReq::parse("==0.7.1").unwrap();
878 assert_eq!(req.to_string(), "==0.7.1");
879
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, <0.7.2");
894 }
895
896 #[tokio::test]
897 async fn package_version_req_devver_roundtrips() {
898 let req = PackageVersionReq::parse("==scm").unwrap();
899 assert_eq!(req.to_string(), "==scm");
900
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("==a144124839f027a2d0a95791936c478d047126fc").unwrap();
908 assert_eq!(
909 req.to_string(),
910 "==a144124839f027a2d0a95791936c478d047126fc"
911 );
912 }
913}