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