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
16mod upgrade;
17
18#[derive(Debug, Error, Diagnostic)]
19pub enum VersionReqToVersionError {
20 #[error("cannot parse version from non-exact version requirement '{0}'")]
21 NonExactVersionReq(VersionReq),
22 #[error("cannot parse version from version requirement '*' (any version)")]
23 Any,
24}
25
26#[derive(Clone, Eq, PartialEq, Hash, Debug)]
27pub enum PackageVersion {
28 SemVer(SemVer),
31 DevVer(DevVer),
33 StringVer(StringVer),
37}
38
39impl HasModRev for PackageVersion {
40 fn to_modrev_string(&self) -> String {
41 match self {
42 Self::SemVer(ver) => ver.to_modrev_string(),
43 Self::DevVer(ver) => ver.to_modrev_string(),
44 Self::StringVer(ver) => ver.to_modrev_string(),
45 }
46 }
47}
48
49impl PackageVersion {
50 pub fn parse(text: &str) -> Result<Self, PackageVersionParseError> {
51 PackageVersion::from_str(text)
52 }
53 pub fn to_version_string(&self) -> String {
55 self.to_modrev_string()
56 }
57 pub fn into_version_req(&self) -> PackageVersionReq {
59 match self {
60 PackageVersion::DevVer(DevVer { modrev, .. }) => {
61 PackageVersionReq::DevVer(modrev.to_owned())
62 }
63 PackageVersion::StringVer(StringVer { modrev, .. }) => {
64 PackageVersionReq::StringVer(modrev.to_owned())
65 }
66 PackageVersion::SemVer(SemVer { version, .. }) => {
67 let version = version.to_owned();
68 PackageVersionReq::SemVer(VersionReq {
69 comparators: vec![Comparator {
70 op: Op::Exact,
71 major: version.major,
72 minor: Some(version.minor),
73 patch: Some(version.patch),
74 pre: version.pre,
75 }],
76 })
77 }
78 }
79 }
80
81 pub(crate) fn to_pessimistic_version_req(&self) -> Option<String> {
86 let PackageVersion::SemVer(sem_ver) = self else {
87 return None;
88 };
89 let version = &sem_ver.version;
90 Some(match (version.major, version.minor) {
91 (0, 0) => format!("~> 0.0.{}", version.patch),
92 (0, minor) => format!("~> 0.{minor}"),
93 (major, _) => format!("~> {major}"),
94 })
95 }
96
97 pub(crate) fn is_semver(&self) -> bool {
98 matches!(self, PackageVersion::SemVer(_))
99 }
100
101 pub(crate) fn default_dev_version() -> Self {
102 Self::DevVer(DevVer::default())
103 }
104
105 pub(crate) fn default_dev_version_with_specrev(specrev: SpecRev) -> Self {
106 Self::DevVer(DevVer::default_with_specrev(specrev))
107 }
108
109 pub(crate) fn with_specrev(mut self, specrev: SpecRev) -> Self {
110 match &mut self {
111 Self::SemVer(version) => version.specrev = specrev,
112 Self::DevVer(version) => version.specrev = specrev,
113 Self::StringVer(version) => version.specrev = specrev,
114 }
115 self
116 }
117}
118
119impl TryFrom<PackageVersionReq> for PackageVersion {
120 type Error = VersionReqToVersionError;
121
122 fn try_from(req: PackageVersionReq) -> Result<Self, Self::Error> {
123 match req {
124 PackageVersionReq::SemVer(version_req) => {
125 match NonEmpty::try_from(version_req.comparators.clone()) {
126 Ok(comparators)
127 if comparators
128 .iter()
129 .any(|comparator| comparator.op != semver::Op::Exact) =>
130 {
131 Err(VersionReqToVersionError::NonExactVersionReq(version_req))
132 }
133 Ok(comparators) => {
134 let comparator = comparators.first();
135 let version = semver::Version {
136 major: comparator.major,
137 minor: comparator.minor.unwrap_or(0),
138 patch: comparator.patch.unwrap_or(0),
139 pre: comparator.pre.clone(),
140 build: semver::BuildMetadata::EMPTY,
141 };
142 Ok(PackageVersion::SemVer(SemVer {
143 version,
144 component_count: comparator_component_count(comparator),
145 specrev: 1.into(),
146 }))
147 }
148 Err(_) => Err(VersionReqToVersionError::NonExactVersionReq(version_req)),
149 }
150 }
151 PackageVersionReq::DevVer(modrev) => Ok(PackageVersion::DevVer(DevVer {
152 modrev,
153 specrev: 1.into(),
154 })),
155 PackageVersionReq::StringVer(modrev) => Ok(PackageVersion::StringVer(StringVer {
156 modrev,
157 specrev: 1.into(),
158 })),
159 PackageVersionReq::Any => Err(VersionReqToVersionError::Any),
160 }
161 }
162}
163
164#[derive(Error, Debug, Diagnostic)]
165#[non_exhaustive]
166pub enum PackageVersionParseError {
167 #[error(transparent)]
168 #[diagnostic(transparent)]
169 Specrev(#[from] SpecrevParseError),
170 #[error("failed to parse version")]
171 Version(#[from] Error),
172}
173
174impl Serialize for PackageVersion {
175 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
176 where
177 S: serde::Serializer,
178 {
179 match self {
180 PackageVersion::SemVer(version) => version.serialize(serializer),
181 PackageVersion::DevVer(version) => version.serialize(serializer),
182 PackageVersion::StringVer(version) => version.serialize(serializer),
183 }
184 }
185}
186
187impl<'de> Deserialize<'de> for PackageVersion {
188 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189 where
190 D: Deserializer<'de>,
191 {
192 let s = String::deserialize(deserializer)?;
193 Self::from_str(&s).map_err(de::Error::custom)
194 }
195}
196
197impl Display for PackageVersion {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 match self {
200 PackageVersion::SemVer(version) => version.fmt(f),
201 PackageVersion::DevVer(version) => version.fmt(f),
202 PackageVersion::StringVer(version) => version.fmt(f),
203 }
204 }
205}
206
207impl PartialOrd for PackageVersion {
208 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
209 Some(self.cmp(other))
210 }
211}
212
213impl Ord for PackageVersion {
214 fn cmp(&self, other: &Self) -> Ordering {
215 match (self, other) {
216 (PackageVersion::SemVer(a), PackageVersion::SemVer(b)) => a.cmp(b),
217 (PackageVersion::SemVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
218 (PackageVersion::SemVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
219 (PackageVersion::DevVer(..), PackageVersion::SemVer(..)) => Ordering::Greater,
220 (PackageVersion::DevVer(a), PackageVersion::DevVer(b)) => a.cmp(b),
221 (PackageVersion::DevVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
222 (PackageVersion::StringVer(a), PackageVersion::StringVer(b)) => a.cmp(b),
223 (PackageVersion::StringVer(..), PackageVersion::SemVer(..)) => Ordering::Less,
224 (PackageVersion::StringVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
225 }
226 }
227}
228
229impl FromStr for PackageVersion {
230 type Err = PackageVersionParseError;
231
232 fn from_str(text: &str) -> Result<Self, Self::Err> {
233 let (modrev, specrev) = split_specrev(text)?;
234 match modrev {
235 "scm" => Ok(PackageVersion::DevVer(DevVer {
236 modrev: DevVersion::Scm,
237 specrev,
238 })),
239 "dev" => Ok(PackageVersion::DevVer(DevVer {
240 modrev: DevVersion::Dev,
241 specrev,
242 })),
243 modrev => match parse_version(modrev) {
244 Ok(version) => Ok(PackageVersion::SemVer(SemVer {
245 component_count: cmp::min(text.chars().filter(|c| *c == '.').count() + 1, 3),
246 version,
247 specrev,
248 })),
249 Err(_) => Ok(PackageVersion::StringVer(StringVer {
250 modrev: modrev.into(),
251 specrev,
252 })),
253 },
254 }
255 }
256}
257
258#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
260pub struct SpecRev(u16);
261
262impl From<u16> for SpecRev {
263 fn from(value: u16) -> Self {
264 Self(value)
265 }
266}
267
268impl Display for SpecRev {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 self.0.fmt(f)
271 }
272}
273
274impl Default for SpecRev {
275 fn default() -> Self {
276 Self(1)
277 }
278}
279
280pub(crate) struct SpecRevIterator {
282 current: u16,
283}
284
285impl SpecRevIterator {
286 pub fn new() -> Self {
287 SpecRevIterator {
288 current: SpecRev::default().0,
289 }
290 }
291}
292
293impl Iterator for SpecRevIterator {
294 type Item = SpecRev;
295
296 fn next(&mut self) -> Option<Self::Item> {
297 let current = self.current;
298 self.current += 1;
299 if self.current == 0 {
300 None } else {
302 Some(SpecRev(current))
303 }
304 }
305}
306
307#[derive(Clone, Eq, PartialEq, Hash, Debug)]
309pub struct SemVer {
310 version: Version,
311 component_count: usize,
312 specrev: SpecRev,
313}
314
315impl HasModRev for SemVer {
316 fn to_modrev_string(&self) -> String {
317 self.version.to_string()
318 }
319}
320
321impl Display for SemVer {
322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323 if self
324 .version
325 .pre
326 .as_str()
327 .chars()
328 .any(|c| c.is_ascii_alphabetic())
329 {
330 return self.version.fmt(f);
332 }
333 let (version_str, remainder) = split_semver_version(&self.version.to_string());
334 let mut luarocks_version_str = version_str.split('.').take(self.component_count).join(".");
335 if let Some(remainder) = remainder {
336 luarocks_version_str.push_str(&format!(".{remainder}"));
340 }
341 let str = format!("{}-{}", luarocks_version_str, self.specrev);
342 str.fmt(f)
343 }
344}
345
346fn split_semver_version(version_str: &str) -> (String, Option<String>) {
347 if let Some(pos) = version_str.rfind('-') {
348 if let Some(pre_build_str) = version_str.get(pos + 1..) {
349 (version_str[..pos].into(), Some(pre_build_str.into()))
350 } else {
351 (version_str[..pos].into(), None)
352 }
353 } else {
354 (version_str.into(), None)
355 }
356}
357
358impl Serialize for SemVer {
359 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
360 where
361 S: serde::Serializer,
362 {
363 self.to_string().serialize(serializer)
364 }
365}
366
367impl Ord for SemVer {
368 fn cmp(&self, other: &Self) -> Ordering {
369 match cmp_versions(&self.version, &other.version) {
370 Ordering::Equal => self.specrev.cmp(&other.specrev),
371 ord => ord,
372 }
373 }
374}
375
376fn cmp_versions(a: &Version, b: &Version) -> Ordering {
380 match (a.major, a.minor, a.patch).cmp(&(b.major, b.minor, b.patch)) {
381 Ordering::Equal => cmp_prerelease(&a.pre, &b.pre),
382 ord => ord,
383 }
384}
385
386fn cmp_prerelease(a: &Prerelease, b: &Prerelease) -> Ordering {
387 match (a.is_empty(), b.is_empty()) {
388 (true, true) => Ordering::Equal,
389 (false, true) => Ordering::Greater,
390 (true, false) => Ordering::Less,
391 (false, false) => a.cmp(b),
392 }
393}
394
395fn comparator_component_count(comparator: &Comparator) -> usize {
396 if comparator.patch.is_some() {
397 3
398 } else if comparator.minor.is_some() {
399 2
400 } else {
401 1
402 }
403}
404
405fn comparator_matches(comparator: &Comparator, version: &Version) -> bool {
409 match comparator.op {
410 Op::Exact | Op::Wildcard => matches_exact(comparator, version),
411 Op::Greater => matches_greater(comparator, version),
412 Op::GreaterEq => matches_greater(comparator, version) || matches_exact(comparator, version),
413 Op::Less => matches_less(comparator, version),
414 Op::LessEq => matches_less(comparator, version) || matches_exact(comparator, version),
415 Op::Tilde => matches_tilde(comparator, version),
416 Op::Caret => matches_caret(comparator, version),
417 _ => false,
418 }
419}
420
421fn matches_exact(comparator: &Comparator, version: &Version) -> bool {
422 if version.major != comparator.major {
423 return false;
424 }
425 if let Some(minor) = comparator.minor {
426 if version.minor != minor {
427 return false;
428 }
429 }
430 if let Some(patch) = comparator.patch {
431 if version.patch != patch {
432 return false;
433 }
434 }
435 version.pre == comparator.pre
436}
437
438fn matches_greater(comparator: &Comparator, version: &Version) -> bool {
439 if version.major != comparator.major {
440 return version.major > comparator.major;
441 }
442 match comparator.minor {
443 None => return false,
444 Some(minor) => {
445 if version.minor != minor {
446 return version.minor > minor;
447 }
448 }
449 }
450 match comparator.patch {
451 None => return false,
452 Some(patch) => {
453 if version.patch != patch {
454 return version.patch > patch;
455 }
456 }
457 }
458 cmp_prerelease(&version.pre, &comparator.pre) == Ordering::Greater
459}
460
461fn matches_less(comparator: &Comparator, version: &Version) -> bool {
462 if version.major != comparator.major {
463 return version.major < comparator.major;
464 }
465 match comparator.minor {
466 None => return false,
467 Some(minor) => {
468 if version.minor != minor {
469 return version.minor < minor;
470 }
471 }
472 }
473 match comparator.patch {
474 None => return false,
475 Some(patch) => {
476 if version.patch != patch {
477 return version.patch < patch;
478 }
479 }
480 }
481 cmp_prerelease(&version.pre, &comparator.pre) == Ordering::Less
482}
483
484fn matches_tilde(comparator: &Comparator, version: &Version) -> bool {
485 if version.major != comparator.major {
486 return false;
487 }
488 if let Some(minor) = comparator.minor {
489 if version.minor != minor {
490 return false;
491 }
492 }
493 if let Some(patch) = comparator.patch {
494 if version.patch != patch {
495 return version.patch > patch;
496 }
497 }
498 cmp_prerelease(&version.pre, &comparator.pre) != Ordering::Less
499}
500
501fn matches_caret(comparator: &Comparator, version: &Version) -> bool {
502 if version.major != comparator.major {
503 return false;
504 }
505 let minor = match comparator.minor {
506 None => return true,
507 Some(minor) => minor,
508 };
509 let patch = match comparator.patch {
510 None => {
511 if comparator.major > 0 {
512 return version.minor >= minor;
513 } else {
514 return version.minor == minor;
515 }
516 }
517 Some(patch) => patch,
518 };
519 if comparator.major > 0 {
520 if version.minor != minor {
521 return version.minor > minor;
522 } else if version.patch != patch {
523 return version.patch > patch;
524 }
525 } else if minor > 0 {
526 if version.minor != minor {
527 return false;
528 } else if version.patch != patch {
529 return version.patch > patch;
530 }
531 } else if version.minor != minor || version.patch != patch {
532 return false;
533 }
534 cmp_prerelease(&version.pre, &comparator.pre) != Ordering::Less
535}
536
537impl PartialOrd for SemVer {
538 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
539 Some(self.cmp(other))
540 }
541}
542
543#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default)]
544#[serde(rename_all = "lowercase")]
545pub enum DevVersion {
546 #[default]
547 Dev,
548 Scm,
549}
550
551impl Display for DevVersion {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 match self {
554 Self::Dev => "dev".fmt(f),
555 Self::Scm => "scm".fmt(f),
556 }
557 }
558}
559
560#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
561pub struct DevVer {
562 modrev: DevVersion,
563 specrev: SpecRev,
564}
565
566impl DevVer {
567 fn default_with_specrev(specrev: SpecRev) -> Self {
568 Self {
569 modrev: DevVersion::default(),
570 specrev,
571 }
572 }
573}
574
575impl HasModRev for DevVer {
576 fn to_modrev_string(&self) -> String {
577 self.modrev.to_string().to_lowercase()
578 }
579}
580
581impl Display for DevVer {
582 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
583 let str = format!("{}-{}", self.modrev, self.specrev);
584 str.fmt(f)
585 }
586}
587
588impl Serialize for DevVer {
589 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
590 where
591 S: serde::Serializer,
592 {
593 self.to_string().serialize(serializer)
594 }
595}
596
597impl Ord for DevVer {
598 fn cmp(&self, other: &Self) -> Ordering {
599 let result = self.specrev.cmp(&other.specrev);
601 if result == Ordering::Equal {
602 return self.modrev.cmp(&other.modrev);
603 }
604 result
605 }
606}
607
608impl PartialOrd for DevVer {
609 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
610 Some(self.cmp(other))
611 }
612}
613
614#[derive(Clone, Eq, PartialEq, Hash, Debug)]
615pub struct StringVer {
616 modrev: String,
617 specrev: SpecRev,
618}
619
620impl HasModRev for StringVer {
621 fn to_modrev_string(&self) -> String {
622 self.modrev.to_string()
623 }
624}
625
626impl Display for StringVer {
627 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
628 let str = format!("{}-{}", self.modrev, self.specrev);
629 str.fmt(f)
630 }
631}
632
633impl Serialize for StringVer {
634 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
635 where
636 S: serde::Serializer,
637 {
638 self.to_string().serialize(serializer)
639 }
640}
641
642impl Ord for StringVer {
643 fn cmp(&self, other: &Self) -> Ordering {
644 let result = self.specrev.cmp(&other.specrev);
646 if result == Ordering::Equal {
647 return self.modrev.cmp(&other.modrev);
648 }
649 result
650 }
651}
652
653impl PartialOrd for StringVer {
654 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
655 Some(self.cmp(other))
656 }
657}
658
659pub(crate) trait HasModRev {
660 fn to_modrev_string(&self) -> String;
663}
664
665#[derive(Error, Debug, Diagnostic)]
666#[non_exhaustive]
667#[error(transparent)]
668pub struct PackageVersionReqError(#[from] Error);
669
670#[derive(Clone, Eq, PartialEq, Hash, Debug)]
673pub enum PackageVersionReq {
674 SemVer(VersionReq),
676 DevVer(DevVersion),
678 StringVer(String),
680 Any,
682}
683
684impl PackageVersionReq {
685 pub fn any() -> Self {
687 PackageVersionReq::Any
688 }
689
690 pub fn parse(text: &str) -> Result<Self, PackageVersionReqError> {
691 PackageVersionReq::from_str(text)
692 }
693
694 pub fn matches(&self, version: &PackageVersion) -> bool {
695 match (self, version) {
696 (PackageVersionReq::SemVer(req), PackageVersion::SemVer(ver)) => req
697 .comparators
698 .iter()
699 .all(|comparator| comparator_matches(comparator, &ver.version)),
700 (PackageVersionReq::DevVer(req), PackageVersion::DevVer(ver)) => req == &ver.modrev,
701 (PackageVersionReq::StringVer(req), PackageVersion::StringVer(ver)) => {
702 req == &ver.modrev
703 }
704 (PackageVersionReq::Any, _) => true,
705 _ => false,
706 }
707 }
708
709 pub fn is_any(&self) -> bool {
710 matches!(self, PackageVersionReq::Any)
711 }
712
713 pub fn is_luau(&self) -> bool {
715 matches!(self, PackageVersionReq::StringVer(req) if req == "luau")
716 }
717}
718
719impl Display for PackageVersionReq {
720 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721 match self {
722 PackageVersionReq::SemVer(version_req) => {
723 let mut str = version_req.to_string();
724 if str.starts_with("=") {
725 str = str.replacen("=", "==", 1);
726 } else if str.starts_with("^") {
727 str = str.replacen("^", "~>", 1);
728 }
729 str.fmt(f)
730 }
731 PackageVersionReq::DevVer(name_req) => write!(f, "=={}", name_req),
732 PackageVersionReq::StringVer(name_req) => write!(f, "=={}", name_req),
733 PackageVersionReq::Any => f.write_str("any"),
734 }
735 }
736}
737
738impl<'de> Deserialize<'de> for PackageVersionReq {
739 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
740 where
741 D: Deserializer<'de>,
742 {
743 String::deserialize(deserializer)?
744 .parse()
745 .map_err(serde::de::Error::custom)
746 }
747}
748
749impl FromStr for PackageVersionReq {
750 type Err = PackageVersionReqError;
751
752 fn from_str(text: &str) -> Result<Self, Self::Err> {
753 let text = correct_version_req_str(text);
754
755 let trimmed = text.trim_start_matches('=').trim_start_matches('@').trim();
756
757 match parse_version_req(&text) {
758 Ok(ver) if ver.comparators.is_empty() => Ok(PackageVersionReq::Any),
759 Ok(ver) => Ok(PackageVersionReq::SemVer(ver)),
760 Err(_) => match trimmed {
761 "scm" => Ok(PackageVersionReq::DevVer(DevVersion::Scm)),
762 "dev" => Ok(PackageVersionReq::DevVer(DevVersion::Dev)),
763 ver => Ok(PackageVersionReq::StringVer(ver.to_string())),
764 },
765 }
766 }
767}
768
769fn correct_version_req_str(text: &str) -> String {
770 text.chars()
771 .chunk_by(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
772 .into_iter()
773 .map(|(is_version_str, chars)| (is_version_str, chars.collect::<String>()))
774 .map(|(is_version_str, chunk)| {
775 if is_version_str && !is_known_dev_version_str(&chunk) {
776 let version_str = trim_specrev(&chunk);
777 correct_prerelease_version_string(version_str)
778 } else {
779 chunk
780 }
781 })
782 .collect::<String>()
783}
784
785fn trim_specrev(version_str: &str) -> &str {
786 if let Some(pos) = version_str.rfind('-') {
787 let suffix = &version_str[pos + 1..];
788 if suffix.chars().all(|c| c.is_ascii_digit()) {
789 &version_str[..pos]
790 } else {
791 version_str
792 }
793 } else {
794 version_str
795 }
796}
797
798#[derive(Error, Debug, Diagnostic)]
799#[non_exhaustive]
800pub enum SpecrevParseError {
801 #[error("specrev {specrev} in version {full_version} contains non-numeric characters")]
802 InvalidSpecrev {
803 specrev: String,
804 full_version: String,
805 },
806 #[error("could not parse specrev in version {0}")]
807 InvalidVersion(String),
808}
809
810fn split_specrev(version_str: &str) -> Result<(&str, SpecRev), SpecrevParseError> {
811 if let Some(pos) = version_str.rfind('-') {
812 if let Some(specrev_str) = version_str.get(pos + 1..) {
813 if specrev_str.chars().all(|c| c.is_ascii_digit()) {
814 let specrev =
815 specrev_str
816 .parse::<u16>()
817 .map_err(|_| SpecrevParseError::InvalidSpecrev {
818 specrev: specrev_str.into(),
819 full_version: version_str.into(),
820 })?;
821 Ok((&version_str[..pos], specrev.into()))
822 } else {
823 Ok((version_str, 1.into()))
825 }
826 } else {
827 Err(SpecrevParseError::InvalidVersion(version_str.into()))
828 }
829 } else {
830 Ok((version_str, 1.into()))
832 }
833}
834
835fn is_known_dev_version_str(text: &str) -> bool {
836 matches!(text, "dev" | "scm")
837}
838
839fn parse_version(s: &str) -> Result<Version, Error> {
842 let version_str = correct_version_string(s);
843 Version::parse(&version_str)
844}
845
846fn parse_version_req(version_constraints: &str) -> Result<VersionReq, Error> {
848 let unescaped = decode_html_entities(version_constraints)
849 .to_string()
850 .as_str()
851 .to_owned();
852 let transformed = match unescaped {
853 s if s.starts_with("~>") => parse_pessimistic_version_constraint(s)?,
854 s if s.starts_with("@") => format!("={}", &s[1..]),
855 s if s.starts_with("==") => s[1..].to_string(),
857 s if s .find(|c: char| c.is_alphanumeric())
859 .is_some_and(|idx| idx == 0) =>
860 {
861 format!("={}", s)
862 }
863 s => s,
864 };
865
866 let version_req = VersionReq::parse(&transformed)?;
867 Ok(version_req)
868}
869
870fn parse_pessimistic_version_constraint(version_constraint: String) -> Result<String, Error> {
871 let min_version_str = &version_constraint[2..].trim();
873 let min_version = Version::parse(&correct_version_string(min_version_str))?;
874
875 let max_version = match min_version_str.matches('.').count() {
876 0 => Version {
877 major: &min_version.major + 1,
878 ..min_version.clone()
879 },
880 1 => Version {
881 minor: &min_version.minor + 1,
882 ..min_version.clone()
883 },
884 _ => Version {
885 patch: &min_version.patch + 1,
886 ..min_version.clone()
887 },
888 };
889
890 Ok(format!(">= {min_version}, < {max_version}"))
891}
892
893fn correct_version_string(version: &str) -> String {
898 let version = append_minor_patch_if_missing(version);
899 correct_prerelease_version_string(&version)
900}
901
902fn correct_prerelease_version_string(version: &str) -> String {
903 if version.contains('-') {
904 return version.to_string();
905 }
906 let parts: Vec<&str> = version.split('.').collect();
907 if parts.len() > 3 {
908 format!(
909 "{}.{}.{}-{}",
910 parts[0],
911 parts[1],
912 parts[2],
913 parts[3..].join(".")
914 )
915 } else {
916 version.to_string()
917 }
918}
919
920fn append_minor_patch_if_missing(version: &str) -> String {
922 if version.matches('.').count() < 2 {
923 append_minor_patch_if_missing(&format!("{version}.0"))
924 } else {
925 version.to_string()
926 }
927}
928
929#[cfg(test)]
930mod tests {
931 use super::*;
932
933 #[test]
934 fn parse_semver_version() {
935 assert_eq!(
936 PackageVersion::parse("1-1").unwrap(),
937 PackageVersion::SemVer(SemVer {
938 version: "1.0.0".parse().unwrap(),
939 component_count: 1,
940 specrev: 1.into(),
941 })
942 );
943 assert_eq!(
944 PackageVersion::parse("1.0-1").unwrap(),
945 PackageVersion::SemVer(SemVer {
946 version: "1.0.0".parse().unwrap(),
947 component_count: 2,
948 specrev: 1.into(),
949 })
950 );
951 assert_eq!(
952 PackageVersion::parse("1.0.0-1").unwrap(),
953 PackageVersion::SemVer(SemVer {
954 version: "1.0.0".parse().unwrap(),
955 component_count: 3,
956 specrev: 1.into()
957 })
958 );
959 assert_eq!(
960 PackageVersion::parse("1.0.0-1").unwrap(),
961 PackageVersion::SemVer(SemVer {
962 version: "1.0.0".parse().unwrap(),
963 component_count: 3,
964 specrev: 1.into()
965 })
966 );
967 assert_eq!(
968 PackageVersion::parse("1.0.0-10-1").unwrap(),
969 PackageVersion::SemVer(SemVer {
970 version: "1.0.0-10".parse().unwrap(),
971 component_count: 3,
972 specrev: 1.into()
973 })
974 );
975 assert_eq!(
976 PackageVersion::parse("1.0.0.10-1").unwrap(),
977 PackageVersion::SemVer(SemVer {
978 version: "1.0.0-10".parse().unwrap(),
979 component_count: 3,
980 specrev: 1.into()
981 })
982 );
983 assert_eq!(
984 PackageVersion::parse("1.0.0.10.0-1").unwrap(),
985 PackageVersion::SemVer(SemVer {
986 version: "1.0.0-10.0".parse().unwrap(),
987 component_count: 3,
988 specrev: 1.into()
989 })
990 );
991 }
992
993 #[test]
994 fn to_pessimistic_version_req() {
995 for (version, req, expected) in [
996 ("1.5.6-2", "~> 1", ">=1.0.0, <2.0.0"),
997 ("0.5.6-2", "~> 0.5", ">=0.5.0, <0.6.0"),
998 ("0.0.6-2", "~> 0.0.6", ">=0.0.6, <0.0.7"),
999 ] {
1000 let pessimistic = PackageVersion::parse(version)
1001 .unwrap()
1002 .to_pessimistic_version_req()
1003 .unwrap();
1004 assert_eq!(pessimistic, req);
1005 let req = PackageVersionReq::parse(&pessimistic).unwrap();
1006 assert_eq!(req.to_string(), expected);
1007 }
1008
1009 let req = PackageVersionReq::parse("~> 1").unwrap();
1010 assert!(req.matches(&PackageVersion::parse("1.9.0-2").unwrap()));
1011 assert!(!req.matches(&PackageVersion::parse("2.0.0-1").unwrap()));
1012
1013 let req = PackageVersionReq::parse("~> 0.5").unwrap();
1014 assert!(req.matches(&PackageVersion::parse("0.5.9-1").unwrap()));
1015 assert!(!req.matches(&PackageVersion::parse("0.6.0-1").unwrap()));
1016
1017 assert_eq!(
1018 PackageVersion::parse("scm")
1019 .unwrap()
1020 .to_pessimistic_version_req(),
1021 None
1022 );
1023 }
1024
1025 #[test]
1026 fn four_component_versions_sort_after_base_version() {
1027 let base = PackageVersion::parse("2.1.0-1").unwrap();
1028 let extended = PackageVersion::parse("2.1.0.10-1").unwrap();
1029 assert!(extended > base);
1030 assert!(base < extended);
1031 assert_eq!(
1032 PackageVersion::parse("2.1.0.10-1").unwrap(),
1033 PackageVersion::parse("2.1.0.10-1").unwrap()
1034 );
1035 }
1036
1037 #[test]
1038 fn four_component_version_satisfies_greater_eq_req() {
1039 let req = PackageVersionReq::parse(">= 2.1.0").unwrap();
1040 assert!(req.matches(&PackageVersion::parse("2.1.0.10-1").unwrap()));
1041 assert!(req.matches(&PackageVersion::parse("2.1.0-1").unwrap()));
1042 assert!(!req.matches(&PackageVersion::parse("2.0.9-1").unwrap()));
1043 }
1044
1045 #[test]
1046 fn parse_semver_prerelease() {
1047 let prerelease = PackageVersion::parse("1.2.3-alpha.1").unwrap();
1048 assert_eq!(
1049 prerelease,
1050 PackageVersion::SemVer(SemVer {
1051 version: "1.2.3-alpha.1".parse().unwrap(),
1052 component_count: 3,
1053 specrev: 1.into()
1054 })
1055 );
1056 assert_eq!(prerelease.to_string(), "1.2.3-alpha.1");
1057 assert_eq!(
1058 PackageVersion::parse("1.2.3-1").unwrap().to_string(),
1059 "1.2.3-1"
1060 );
1061 assert_eq!(
1062 PackageVersion::parse(&prerelease.to_string()).unwrap(),
1063 prerelease
1064 );
1065 }
1066
1067 #[test]
1068 fn prerelease_req_matches() {
1069 let req = PackageVersionReq::parse("=1.2.3-alpha.1").unwrap();
1070 assert!(req.matches(&PackageVersion::parse("1.2.3-alpha.1").unwrap()));
1071 assert!(!req.matches(&PackageVersion::parse("1.2.3-alpha.2").unwrap()));
1072 }
1073
1074 #[test]
1075 fn parse_dev_version() {
1076 assert_eq!(
1077 PackageVersion::parse("dev-1").unwrap(),
1078 PackageVersion::DevVer(DevVer {
1079 modrev: DevVersion::Dev,
1080 specrev: 1.into()
1081 })
1082 );
1083 assert_eq!(
1084 PackageVersion::parse("scm-1").unwrap(),
1085 PackageVersion::DevVer(DevVer {
1086 modrev: DevVersion::Scm,
1087 specrev: 1.into()
1088 })
1089 );
1090 assert_eq!(
1091 PackageVersion::parse("git-1").unwrap(),
1092 PackageVersion::StringVer(StringVer {
1093 modrev: "git".into(),
1094 specrev: 1.into()
1095 })
1096 );
1097 assert_eq!(
1098 PackageVersion::parse("scm-1").unwrap(),
1099 PackageVersion::DevVer(DevVer {
1100 modrev: DevVersion::Scm,
1101 specrev: 1.into()
1102 })
1103 );
1104 }
1105
1106 #[test]
1107 fn parse_dev_version_req() {
1108 assert_eq!(
1109 PackageVersionReq::parse("dev").unwrap(),
1110 PackageVersionReq::DevVer(DevVersion::Dev)
1111 );
1112 assert_eq!(
1113 PackageVersionReq::parse("scm").unwrap(),
1114 PackageVersionReq::DevVer(DevVersion::Scm)
1115 );
1116 assert_eq!(
1117 PackageVersionReq::parse("git").unwrap(),
1118 PackageVersionReq::StringVer("git".into())
1119 );
1120 assert_eq!(
1121 PackageVersionReq::parse("==dev").unwrap(),
1122 PackageVersionReq::DevVer(DevVersion::Dev)
1123 );
1124 assert_eq!(
1125 PackageVersionReq::parse("==git").unwrap(),
1126 PackageVersionReq::StringVer("git".into())
1127 );
1128 assert_eq!(
1129 PackageVersionReq::parse("== dev").unwrap(),
1130 PackageVersionReq::DevVer(DevVersion::Dev)
1131 );
1132 assert_eq!(
1133 PackageVersionReq::parse("== scm").unwrap(),
1134 PackageVersionReq::DevVer(DevVersion::Scm)
1135 );
1136 assert_eq!(
1137 PackageVersionReq::parse("@dev").unwrap(),
1138 PackageVersionReq::DevVer(DevVersion::Dev)
1139 );
1140 assert_eq!(
1141 PackageVersionReq::parse("@git").unwrap(),
1142 PackageVersionReq::StringVer("git".into())
1143 );
1144 assert_eq!(
1145 PackageVersionReq::parse("@ dev").unwrap(),
1146 PackageVersionReq::DevVer(DevVersion::Dev)
1147 );
1148 assert_eq!(
1149 PackageVersionReq::parse("@ scm").unwrap(),
1150 PackageVersionReq::DevVer(DevVersion::Scm)
1151 );
1152 assert_eq!(
1153 PackageVersionReq::parse(">1-1,<1.2-2").unwrap(),
1154 PackageVersionReq::SemVer(">1,<1.2".parse().unwrap())
1155 );
1156 assert_eq!(
1157 PackageVersionReq::parse("> 1-1, < 1.2-2").unwrap(),
1158 PackageVersionReq::SemVer("> 1, < 1.2".parse().unwrap())
1159 );
1160 assert_eq!(
1161 PackageVersionReq::parse("> 2.1.0.10, < 2.1.1").unwrap(),
1162 PackageVersionReq::SemVer("> 2.1.0-10, < 2.1.1".parse().unwrap())
1163 );
1164 }
1165
1166 #[test]
1167 fn parse_any_version_req() {
1168 assert_eq!(
1169 PackageVersionReq::parse("*").unwrap(),
1170 PackageVersionReq::Any
1171 );
1172 }
1173
1174 #[test]
1175 fn package_version_req_semver_roundtrips() {
1176 let req = PackageVersionReq::parse("==0.7.1").unwrap();
1177 assert_eq!(req.to_string(), "==0.7.1");
1178
1179 let req = PackageVersionReq::parse("0.7.1").unwrap();
1180 assert_eq!(req.to_string(), "==0.7.1");
1181
1182 let req = PackageVersionReq::parse(">=0.7.1").unwrap();
1183 assert_eq!(req.to_string(), ">=0.7.1");
1184
1185 let req = PackageVersionReq::parse(">0.7.1").unwrap();
1186 assert_eq!(req.to_string(), ">0.7.1");
1187
1188 let req = PackageVersionReq::parse("<0.7.1").unwrap();
1189 assert_eq!(req.to_string(), "<0.7.1");
1190
1191 let req = PackageVersionReq::parse("~> 0.7.1").unwrap();
1192 assert_eq!(req.to_string(), ">=0.7.1, <0.7.2");
1193 }
1194
1195 #[test]
1196 fn package_version_req_devver_roundtrips() {
1197 let req = PackageVersionReq::parse("==scm").unwrap();
1198 assert_eq!(req.to_string(), "==scm");
1199
1200 let req = PackageVersionReq::parse("@scm").unwrap();
1201 assert_eq!(req.to_string(), "==scm");
1202
1203 let req = PackageVersionReq::parse("scm").unwrap();
1204 assert_eq!(req.to_string(), "==scm");
1205
1206 let req = PackageVersionReq::parse("==a144124839f027a2d0a95791936c478d047126fc").unwrap();
1207 assert_eq!(
1208 req.to_string(),
1209 "==a144124839f027a2d0a95791936c478d047126fc"
1210 );
1211 }
1212}