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