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, Prerelease, 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")]
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 if self
299 .version
300 .pre
301 .as_str()
302 .chars()
303 .any(|c| c.is_ascii_alphabetic())
304 {
305 return self.version.fmt(f);
307 }
308 let (version_str, remainder) = split_semver_version(&self.version.to_string());
309 let mut luarocks_version_str = version_str.split('.').take(self.component_count).join(".");
310 if let Some(remainder) = remainder {
311 luarocks_version_str.push_str(&format!(".{remainder}"));
315 }
316 let str = format!("{}-{}", luarocks_version_str, self.specrev);
317 str.fmt(f)
318 }
319}
320
321fn split_semver_version(version_str: &str) -> (String, Option<String>) {
322 if let Some(pos) = version_str.rfind('-') {
323 if let Some(pre_build_str) = version_str.get(pos + 1..) {
324 (version_str[..pos].into(), Some(pre_build_str.into()))
325 } else {
326 (version_str[..pos].into(), None)
327 }
328 } else {
329 (version_str.into(), None)
330 }
331}
332
333impl Serialize for SemVer {
334 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
335 where
336 S: serde::Serializer,
337 {
338 self.to_string().serialize(serializer)
339 }
340}
341
342impl Ord for SemVer {
343 fn cmp(&self, other: &Self) -> Ordering {
344 match cmp_versions(&self.version, &other.version) {
345 Ordering::Equal => self.specrev.cmp(&other.specrev),
346 ord => ord,
347 }
348 }
349}
350
351fn cmp_versions(a: &Version, b: &Version) -> Ordering {
355 match (a.major, a.minor, a.patch).cmp(&(b.major, b.minor, b.patch)) {
356 Ordering::Equal => cmp_prerelease(&a.pre, &b.pre),
357 ord => ord,
358 }
359}
360
361fn cmp_prerelease(a: &Prerelease, b: &Prerelease) -> Ordering {
362 match (a.is_empty(), b.is_empty()) {
363 (true, true) => Ordering::Equal,
364 (false, true) => Ordering::Greater,
365 (true, false) => Ordering::Less,
366 (false, false) => a.cmp(b),
367 }
368}
369
370fn comparator_matches(comparator: &Comparator, version: &Version) -> bool {
374 match comparator.op {
375 Op::Exact | Op::Wildcard => matches_exact(comparator, version),
376 Op::Greater => matches_greater(comparator, version),
377 Op::GreaterEq => matches_greater(comparator, version) || matches_exact(comparator, version),
378 Op::Less => matches_less(comparator, version),
379 Op::LessEq => matches_less(comparator, version) || matches_exact(comparator, version),
380 Op::Tilde => matches_tilde(comparator, version),
381 Op::Caret => matches_caret(comparator, version),
382 _ => false,
383 }
384}
385
386fn matches_exact(comparator: &Comparator, version: &Version) -> bool {
387 if version.major != comparator.major {
388 return false;
389 }
390 if let Some(minor) = comparator.minor {
391 if version.minor != minor {
392 return false;
393 }
394 }
395 if let Some(patch) = comparator.patch {
396 if version.patch != patch {
397 return false;
398 }
399 }
400 version.pre == comparator.pre
401}
402
403fn matches_greater(comparator: &Comparator, version: &Version) -> bool {
404 if version.major != comparator.major {
405 return version.major > comparator.major;
406 }
407 match comparator.minor {
408 None => return false,
409 Some(minor) => {
410 if version.minor != minor {
411 return version.minor > minor;
412 }
413 }
414 }
415 match comparator.patch {
416 None => return false,
417 Some(patch) => {
418 if version.patch != patch {
419 return version.patch > patch;
420 }
421 }
422 }
423 cmp_prerelease(&version.pre, &comparator.pre) == Ordering::Greater
424}
425
426fn matches_less(comparator: &Comparator, version: &Version) -> bool {
427 if version.major != comparator.major {
428 return version.major < comparator.major;
429 }
430 match comparator.minor {
431 None => return false,
432 Some(minor) => {
433 if version.minor != minor {
434 return version.minor < minor;
435 }
436 }
437 }
438 match comparator.patch {
439 None => return false,
440 Some(patch) => {
441 if version.patch != patch {
442 return version.patch < patch;
443 }
444 }
445 }
446 cmp_prerelease(&version.pre, &comparator.pre) == Ordering::Less
447}
448
449fn matches_tilde(comparator: &Comparator, version: &Version) -> bool {
450 if version.major != comparator.major {
451 return false;
452 }
453 if let Some(minor) = comparator.minor {
454 if version.minor != minor {
455 return false;
456 }
457 }
458 if let Some(patch) = comparator.patch {
459 if version.patch != patch {
460 return version.patch > patch;
461 }
462 }
463 cmp_prerelease(&version.pre, &comparator.pre) != Ordering::Less
464}
465
466fn matches_caret(comparator: &Comparator, version: &Version) -> bool {
467 if version.major != comparator.major {
468 return false;
469 }
470 let minor = match comparator.minor {
471 None => return true,
472 Some(minor) => minor,
473 };
474 let patch = match comparator.patch {
475 None => {
476 if comparator.major > 0 {
477 return version.minor >= minor;
478 } else {
479 return version.minor == minor;
480 }
481 }
482 Some(patch) => patch,
483 };
484 if comparator.major > 0 {
485 if version.minor != minor {
486 return version.minor > minor;
487 } else if version.patch != patch {
488 return version.patch > patch;
489 }
490 } else if minor > 0 {
491 if version.minor != minor {
492 return false;
493 } else if version.patch != patch {
494 return version.patch > patch;
495 }
496 } else if version.minor != minor || version.patch != patch {
497 return false;
498 }
499 cmp_prerelease(&version.pre, &comparator.pre) != Ordering::Less
500}
501
502impl PartialOrd for SemVer {
503 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
504 Some(self.cmp(other))
505 }
506}
507
508#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default)]
509#[serde(rename_all = "lowercase")]
510pub enum DevVersion {
511 #[default]
512 Dev,
513 Scm,
514}
515
516impl Display for DevVersion {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 match self {
519 Self::Dev => "dev".fmt(f),
520 Self::Scm => "scm".fmt(f),
521 }
522 }
523}
524
525#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
526pub struct DevVer {
527 modrev: DevVersion,
528 specrev: SpecRev,
529}
530
531impl DevVer {
532 fn default_with_specrev(specrev: SpecRev) -> Self {
533 Self {
534 modrev: DevVersion::default(),
535 specrev,
536 }
537 }
538}
539
540impl HasModRev for DevVer {
541 fn to_modrev_string(&self) -> String {
542 self.modrev.to_string().to_lowercase()
543 }
544}
545
546impl Display for DevVer {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 let str = format!("{}-{}", self.modrev, self.specrev);
549 str.fmt(f)
550 }
551}
552
553impl Serialize for DevVer {
554 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
555 where
556 S: serde::Serializer,
557 {
558 self.to_string().serialize(serializer)
559 }
560}
561
562impl Ord for DevVer {
563 fn cmp(&self, other: &Self) -> Ordering {
564 let result = self.specrev.cmp(&other.specrev);
566 if result == Ordering::Equal {
567 return self.modrev.cmp(&other.modrev);
568 }
569 result
570 }
571}
572
573impl PartialOrd for DevVer {
574 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
575 Some(self.cmp(other))
576 }
577}
578
579#[derive(Clone, Eq, PartialEq, Hash, Debug)]
580pub struct StringVer {
581 modrev: String,
582 specrev: SpecRev,
583}
584
585impl HasModRev for StringVer {
586 fn to_modrev_string(&self) -> String {
587 self.modrev.to_string()
588 }
589}
590
591impl Display for StringVer {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 let str = format!("{}-{}", self.modrev, self.specrev);
594 str.fmt(f)
595 }
596}
597
598impl Serialize for StringVer {
599 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
600 where
601 S: serde::Serializer,
602 {
603 self.to_string().serialize(serializer)
604 }
605}
606
607impl Ord for StringVer {
608 fn cmp(&self, other: &Self) -> Ordering {
609 let result = self.specrev.cmp(&other.specrev);
611 if result == Ordering::Equal {
612 return self.modrev.cmp(&other.modrev);
613 }
614 result
615 }
616}
617
618impl PartialOrd for StringVer {
619 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
620 Some(self.cmp(other))
621 }
622}
623
624pub(crate) trait HasModRev {
625 fn to_modrev_string(&self) -> String;
628}
629
630#[derive(Error, Debug, Diagnostic)]
631#[non_exhaustive]
632#[error(transparent)]
633pub struct PackageVersionReqError(#[from] Error);
634
635#[derive(Clone, Eq, PartialEq, Hash, Debug)]
638pub enum PackageVersionReq {
639 SemVer(VersionReq),
641 DevVer(DevVersion),
643 StringVer(String),
645 Any,
647}
648
649impl PackageVersionReq {
650 pub fn any() -> Self {
652 PackageVersionReq::Any
653 }
654
655 pub fn parse(text: &str) -> Result<Self, PackageVersionReqError> {
656 PackageVersionReq::from_str(text)
657 }
658
659 pub fn matches(&self, version: &PackageVersion) -> bool {
660 match (self, version) {
661 (PackageVersionReq::SemVer(req), PackageVersion::SemVer(ver)) => req
662 .comparators
663 .iter()
664 .all(|comparator| comparator_matches(comparator, &ver.version)),
665 (PackageVersionReq::DevVer(req), PackageVersion::DevVer(ver)) => req == &ver.modrev,
666 (PackageVersionReq::StringVer(req), PackageVersion::StringVer(ver)) => {
667 req == &ver.modrev
668 }
669 (PackageVersionReq::Any, _) => true,
670 _ => false,
671 }
672 }
673
674 pub fn is_any(&self) -> bool {
675 matches!(self, PackageVersionReq::Any)
676 }
677}
678
679impl Display for PackageVersionReq {
680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681 match self {
682 PackageVersionReq::SemVer(version_req) => {
683 let mut str = version_req.to_string();
684 if str.starts_with("=") {
685 str = str.replacen("=", "==", 1);
686 } else if str.starts_with("^") {
687 str = str.replacen("^", "~>", 1);
688 }
689 str.fmt(f)
690 }
691 PackageVersionReq::DevVer(name_req) => write!(f, "=={}", name_req),
692 PackageVersionReq::StringVer(name_req) => write!(f, "=={}", name_req),
693 PackageVersionReq::Any => f.write_str("any"),
694 }
695 }
696}
697
698impl<'de> Deserialize<'de> for PackageVersionReq {
699 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
700 where
701 D: Deserializer<'de>,
702 {
703 String::deserialize(deserializer)?
704 .parse()
705 .map_err(serde::de::Error::custom)
706 }
707}
708
709impl FromStr for PackageVersionReq {
710 type Err = PackageVersionReqError;
711
712 fn from_str(text: &str) -> Result<Self, Self::Err> {
713 let text = correct_version_req_str(text);
714
715 let trimmed = text.trim_start_matches('=').trim_start_matches('@').trim();
716
717 match parse_version_req(&text) {
718 Ok(_) => Ok(PackageVersionReq::SemVer(parse_version_req(&text)?)),
719 Err(_) => match trimmed {
720 "scm" => Ok(PackageVersionReq::DevVer(DevVersion::Scm)),
721 "dev" => Ok(PackageVersionReq::DevVer(DevVersion::Dev)),
722 ver => Ok(PackageVersionReq::StringVer(ver.to_string())),
723 },
724 }
725 }
726}
727
728fn correct_version_req_str(text: &str) -> String {
729 text.chars()
730 .chunk_by(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
731 .into_iter()
732 .map(|(is_version_str, chars)| (is_version_str, chars.collect::<String>()))
733 .map(|(is_version_str, chunk)| {
734 if is_version_str && !is_known_dev_version_str(&chunk) {
735 let version_str = trim_specrev(&chunk);
736 correct_prerelease_version_string(version_str)
737 } else {
738 chunk
739 }
740 })
741 .collect::<String>()
742}
743
744fn trim_specrev(version_str: &str) -> &str {
745 if let Some(pos) = version_str.rfind('-') {
746 let suffix = &version_str[pos + 1..];
747 if suffix.chars().all(|c| c.is_ascii_digit()) {
748 &version_str[..pos]
749 } else {
750 version_str
751 }
752 } else {
753 version_str
754 }
755}
756
757#[derive(Error, Debug, Diagnostic)]
758#[non_exhaustive]
759pub enum SpecrevParseError {
760 #[error("specrev {specrev} in version {full_version} contains non-numeric characters")]
761 InvalidSpecrev {
762 specrev: String,
763 full_version: String,
764 },
765 #[error("could not parse specrev in version {0}")]
766 InvalidVersion(String),
767}
768
769fn split_specrev(version_str: &str) -> Result<(&str, SpecRev), SpecrevParseError> {
770 if let Some(pos) = version_str.rfind('-') {
771 if let Some(specrev_str) = version_str.get(pos + 1..) {
772 if specrev_str.chars().all(|c| c.is_ascii_digit()) {
773 let specrev =
774 specrev_str
775 .parse::<u16>()
776 .map_err(|_| SpecrevParseError::InvalidSpecrev {
777 specrev: specrev_str.into(),
778 full_version: version_str.into(),
779 })?;
780 Ok((&version_str[..pos], specrev.into()))
781 } else {
782 Ok((version_str, 1.into()))
784 }
785 } else {
786 Err(SpecrevParseError::InvalidVersion(version_str.into()))
787 }
788 } else {
789 Ok((version_str, 1.into()))
791 }
792}
793
794fn is_known_dev_version_str(text: &str) -> bool {
795 matches!(text, "dev" | "scm")
796}
797
798fn parse_version(s: &str) -> Result<Version, Error> {
801 let version_str = correct_version_string(s);
802 Version::parse(&version_str)
803}
804
805fn parse_version_req(version_constraints: &str) -> Result<VersionReq, Error> {
807 let unescaped = decode_html_entities(version_constraints)
808 .to_string()
809 .as_str()
810 .to_owned();
811 let transformed = match unescaped {
812 s if s.starts_with("~>") => parse_pessimistic_version_constraint(s)?,
813 s if s.starts_with("@") => format!("={}", &s[1..]),
814 s if s.starts_with("==") => s[1..].to_string(),
816 s if s .find(|c: char| c.is_alphanumeric())
818 .is_some_and(|idx| idx == 0) =>
819 {
820 format!("={}", s)
821 }
822 s => s,
823 };
824
825 let version_req = VersionReq::parse(&transformed)?;
826 Ok(version_req)
827}
828
829fn parse_pessimistic_version_constraint(version_constraint: String) -> Result<String, Error> {
830 let min_version_str = &version_constraint[2..].trim();
832 let min_version = Version::parse(&correct_version_string(min_version_str))?;
833
834 let max_version = match min_version_str.matches('.').count() {
835 0 => Version {
836 major: &min_version.major + 1,
837 ..min_version.clone()
838 },
839 1 => Version {
840 minor: &min_version.minor + 1,
841 ..min_version.clone()
842 },
843 _ => Version {
844 patch: &min_version.patch + 1,
845 ..min_version.clone()
846 },
847 };
848
849 Ok(format!(">= {min_version}, < {max_version}"))
850}
851
852fn correct_version_string(version: &str) -> String {
857 let version = append_minor_patch_if_missing(version);
858 correct_prerelease_version_string(&version)
859}
860
861fn correct_prerelease_version_string(version: &str) -> String {
862 if version.contains('-') {
863 return version.to_string();
864 }
865 let parts: Vec<&str> = version.split('.').collect();
866 if parts.len() > 3 {
867 format!(
868 "{}.{}.{}-{}",
869 parts[0],
870 parts[1],
871 parts[2],
872 parts[3..].join(".")
873 )
874 } else {
875 version.to_string()
876 }
877}
878
879fn append_minor_patch_if_missing(version: &str) -> String {
881 if version.matches('.').count() < 2 {
882 append_minor_patch_if_missing(&format!("{version}.0"))
883 } else {
884 version.to_string()
885 }
886}
887
888#[cfg(test)]
889mod tests {
890 use super::*;
891
892 #[tokio::test]
893 async fn parse_semver_version() {
894 assert_eq!(
895 PackageVersion::parse("1-1").unwrap(),
896 PackageVersion::SemVer(SemVer {
897 version: "1.0.0".parse().unwrap(),
898 component_count: 1,
899 specrev: 1.into(),
900 })
901 );
902 assert_eq!(
903 PackageVersion::parse("1.0-1").unwrap(),
904 PackageVersion::SemVer(SemVer {
905 version: "1.0.0".parse().unwrap(),
906 component_count: 2,
907 specrev: 1.into(),
908 })
909 );
910 assert_eq!(
911 PackageVersion::parse("1.0.0-1").unwrap(),
912 PackageVersion::SemVer(SemVer {
913 version: "1.0.0".parse().unwrap(),
914 component_count: 3,
915 specrev: 1.into()
916 })
917 );
918 assert_eq!(
919 PackageVersion::parse("1.0.0-1").unwrap(),
920 PackageVersion::SemVer(SemVer {
921 version: "1.0.0".parse().unwrap(),
922 component_count: 3,
923 specrev: 1.into()
924 })
925 );
926 assert_eq!(
927 PackageVersion::parse("1.0.0-10-1").unwrap(),
928 PackageVersion::SemVer(SemVer {
929 version: "1.0.0-10".parse().unwrap(),
930 component_count: 3,
931 specrev: 1.into()
932 })
933 );
934 assert_eq!(
935 PackageVersion::parse("1.0.0.10-1").unwrap(),
936 PackageVersion::SemVer(SemVer {
937 version: "1.0.0-10".parse().unwrap(),
938 component_count: 3,
939 specrev: 1.into()
940 })
941 );
942 assert_eq!(
943 PackageVersion::parse("1.0.0.10.0-1").unwrap(),
944 PackageVersion::SemVer(SemVer {
945 version: "1.0.0-10.0".parse().unwrap(),
946 component_count: 3,
947 specrev: 1.into()
948 })
949 );
950 }
951
952 #[test]
953 fn four_component_versions_sort_after_base_version() {
954 let base = PackageVersion::parse("2.1.0-1").unwrap();
955 let extended = PackageVersion::parse("2.1.0.10-1").unwrap();
956 assert!(extended > base);
957 assert!(base < extended);
958 assert_eq!(
959 PackageVersion::parse("2.1.0.10-1").unwrap(),
960 PackageVersion::parse("2.1.0.10-1").unwrap()
961 );
962 }
963
964 #[test]
965 fn four_component_version_satisfies_greater_eq_req() {
966 let req = PackageVersionReq::parse(">= 2.1.0").unwrap();
967 assert!(req.matches(&PackageVersion::parse("2.1.0.10-1").unwrap()));
968 assert!(req.matches(&PackageVersion::parse("2.1.0-1").unwrap()));
969 assert!(!req.matches(&PackageVersion::parse("2.0.9-1").unwrap()));
970 }
971
972 #[test]
973 fn parse_semver_prerelease() {
974 let prerelease = PackageVersion::parse("1.2.3-alpha.1").unwrap();
975 assert_eq!(
976 prerelease,
977 PackageVersion::SemVer(SemVer {
978 version: "1.2.3-alpha.1".parse().unwrap(),
979 component_count: 3,
980 specrev: 1.into()
981 })
982 );
983 assert_eq!(prerelease.to_string(), "1.2.3-alpha.1");
984 assert_eq!(
985 PackageVersion::parse("1.2.3-1").unwrap().to_string(),
986 "1.2.3-1"
987 );
988 assert_eq!(
989 PackageVersion::parse(&prerelease.to_string()).unwrap(),
990 prerelease
991 );
992 }
993
994 #[test]
995 fn prerelease_req_matches() {
996 let req = PackageVersionReq::parse("=1.2.3-alpha.1").unwrap();
997 assert!(req.matches(&PackageVersion::parse("1.2.3-alpha.1").unwrap()));
998 assert!(!req.matches(&PackageVersion::parse("1.2.3-alpha.2").unwrap()));
999 }
1000
1001 #[tokio::test]
1002 async fn parse_dev_version() {
1003 assert_eq!(
1004 PackageVersion::parse("dev-1").unwrap(),
1005 PackageVersion::DevVer(DevVer {
1006 modrev: DevVersion::Dev,
1007 specrev: 1.into()
1008 })
1009 );
1010 assert_eq!(
1011 PackageVersion::parse("scm-1").unwrap(),
1012 PackageVersion::DevVer(DevVer {
1013 modrev: DevVersion::Scm,
1014 specrev: 1.into()
1015 })
1016 );
1017 assert_eq!(
1018 PackageVersion::parse("git-1").unwrap(),
1019 PackageVersion::StringVer(StringVer {
1020 modrev: "git".into(),
1021 specrev: 1.into()
1022 })
1023 );
1024 assert_eq!(
1025 PackageVersion::parse("scm-1").unwrap(),
1026 PackageVersion::DevVer(DevVer {
1027 modrev: DevVersion::Scm,
1028 specrev: 1.into()
1029 })
1030 );
1031 }
1032
1033 #[tokio::test]
1034 async fn parse_dev_version_req() {
1035 assert_eq!(
1036 PackageVersionReq::parse("dev").unwrap(),
1037 PackageVersionReq::DevVer(DevVersion::Dev)
1038 );
1039 assert_eq!(
1040 PackageVersionReq::parse("scm").unwrap(),
1041 PackageVersionReq::DevVer(DevVersion::Scm)
1042 );
1043 assert_eq!(
1044 PackageVersionReq::parse("git").unwrap(),
1045 PackageVersionReq::StringVer("git".into())
1046 );
1047 assert_eq!(
1048 PackageVersionReq::parse("==dev").unwrap(),
1049 PackageVersionReq::DevVer(DevVersion::Dev)
1050 );
1051 assert_eq!(
1052 PackageVersionReq::parse("==git").unwrap(),
1053 PackageVersionReq::StringVer("git".into())
1054 );
1055 assert_eq!(
1056 PackageVersionReq::parse("== dev").unwrap(),
1057 PackageVersionReq::DevVer(DevVersion::Dev)
1058 );
1059 assert_eq!(
1060 PackageVersionReq::parse("== scm").unwrap(),
1061 PackageVersionReq::DevVer(DevVersion::Scm)
1062 );
1063 assert_eq!(
1064 PackageVersionReq::parse("@dev").unwrap(),
1065 PackageVersionReq::DevVer(DevVersion::Dev)
1066 );
1067 assert_eq!(
1068 PackageVersionReq::parse("@git").unwrap(),
1069 PackageVersionReq::StringVer("git".into())
1070 );
1071 assert_eq!(
1072 PackageVersionReq::parse("@ dev").unwrap(),
1073 PackageVersionReq::DevVer(DevVersion::Dev)
1074 );
1075 assert_eq!(
1076 PackageVersionReq::parse("@ scm").unwrap(),
1077 PackageVersionReq::DevVer(DevVersion::Scm)
1078 );
1079 assert_eq!(
1080 PackageVersionReq::parse(">1-1,<1.2-2").unwrap(),
1081 PackageVersionReq::SemVer(">1,<1.2".parse().unwrap())
1082 );
1083 assert_eq!(
1084 PackageVersionReq::parse("> 1-1, < 1.2-2").unwrap(),
1085 PackageVersionReq::SemVer("> 1, < 1.2".parse().unwrap())
1086 );
1087 assert_eq!(
1088 PackageVersionReq::parse("> 2.1.0.10, < 2.1.1").unwrap(),
1089 PackageVersionReq::SemVer("> 2.1.0-10, < 2.1.1".parse().unwrap())
1090 );
1091 }
1092
1093 #[tokio::test]
1094 async fn package_version_req_semver_roundtrips() {
1095 let req = PackageVersionReq::parse("==0.7.1").unwrap();
1096 assert_eq!(req.to_string(), "==0.7.1");
1097
1098 let req = PackageVersionReq::parse("0.7.1").unwrap();
1099 assert_eq!(req.to_string(), "==0.7.1");
1100
1101 let req = PackageVersionReq::parse(">=0.7.1").unwrap();
1102 assert_eq!(req.to_string(), ">=0.7.1");
1103
1104 let req = PackageVersionReq::parse(">0.7.1").unwrap();
1105 assert_eq!(req.to_string(), ">0.7.1");
1106
1107 let req = PackageVersionReq::parse("<0.7.1").unwrap();
1108 assert_eq!(req.to_string(), "<0.7.1");
1109
1110 let req = PackageVersionReq::parse("~> 0.7.1").unwrap();
1111 assert_eq!(req.to_string(), ">=0.7.1, <0.7.2");
1112 }
1113
1114 #[tokio::test]
1115 async fn package_version_req_devver_roundtrips() {
1116 let req = PackageVersionReq::parse("==scm").unwrap();
1117 assert_eq!(req.to_string(), "==scm");
1118
1119 let req = PackageVersionReq::parse("@scm").unwrap();
1120 assert_eq!(req.to_string(), "==scm");
1121
1122 let req = PackageVersionReq::parse("scm").unwrap();
1123 assert_eq!(req.to_string(), "==scm");
1124
1125 let req = PackageVersionReq::parse("==a144124839f027a2d0a95791936c478d047126fc").unwrap();
1126 assert_eq!(
1127 req.to_string(),
1128 "==a144124839f027a2d0a95791936c478d047126fc"
1129 );
1130 }
1131}