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 semver::{Comparator, Error, Op, Version, VersionReq};
11use serde::{de, Deserialize, Deserializer, Serialize};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum VersionReqToVersionError {
16 #[error("cannot parse version from non-exact version requirement '{0}'")]
17 NonExactVersionReq(VersionReq),
18 #[error("cannot parse version from version requirement '*' (any version)")]
19 Any,
20}
21
22#[derive(Clone, Eq, PartialEq, Hash, Debug)]
23pub enum PackageVersion {
24 SemVer(SemVer),
27 DevVer(DevVer),
29 StringVer(StringVer),
33}
34
35impl PackageVersion {
36 pub fn parse(text: &str) -> Result<Self, PackageVersionParseError> {
37 PackageVersion::from_str(text)
38 }
39 pub fn into_version_req(&self) -> PackageVersionReq {
41 match self {
42 PackageVersion::DevVer(DevVer { modrev, .. }) => {
43 PackageVersionReq::DevVer(modrev.to_owned())
44 }
45 PackageVersion::StringVer(StringVer { modrev, .. }) => {
46 PackageVersionReq::StringVer(modrev.to_owned())
47 }
48 PackageVersion::SemVer(SemVer { version, .. }) => {
49 let version = version.to_owned();
50 PackageVersionReq::SemVer(VersionReq {
51 comparators: vec![Comparator {
52 op: Op::Exact,
53 major: version.major,
54 minor: Some(version.minor),
55 patch: Some(version.patch),
56 pre: version.pre,
57 }],
58 })
59 }
60 }
61 }
62
63 pub(crate) fn is_semver(&self) -> bool {
64 matches!(self, PackageVersion::SemVer(_))
65 }
66
67 pub(crate) fn default_dev_version() -> Self {
68 Self::DevVer(DevVer::default())
69 }
70}
71
72impl TryFrom<PackageVersionReq> for PackageVersion {
73 type Error = VersionReqToVersionError;
74
75 fn try_from(req: PackageVersionReq) -> Result<Self, Self::Error> {
76 match req {
77 PackageVersionReq::SemVer(version_req) => {
78 if version_req.comparators.is_empty()
79 || version_req
80 .comparators
81 .iter()
82 .any(|comparator| comparator.op != semver::Op::Exact)
83 {
84 Err(VersionReqToVersionError::NonExactVersionReq(
85 version_req.clone(),
86 ))
87 } else {
88 let comparator = version_req.comparators.first().unwrap();
89 let version = semver::Version {
90 major: comparator.major,
91 minor: comparator.minor.unwrap_or(0),
92 patch: comparator.patch.unwrap_or(0),
93 pre: comparator.pre.clone(),
94 build: semver::BuildMetadata::EMPTY,
95 };
96 let component_count = if comparator.patch.is_some() {
97 3
98 } else if comparator.minor.is_some() {
99 2
100 } else {
101 1
102 };
103 Ok(PackageVersion::SemVer(SemVer {
104 version,
105 component_count,
106 specrev: 1,
107 }))
108 }
109 }
110 PackageVersionReq::DevVer(modrev) => {
111 Ok(PackageVersion::DevVer(DevVer { modrev, specrev: 1 }))
112 }
113 PackageVersionReq::StringVer(modrev) => {
114 Ok(PackageVersion::StringVer(StringVer { modrev, specrev: 1 }))
115 }
116 PackageVersionReq::Any => Err(VersionReqToVersionError::Any),
117 }
118 }
119}
120
121impl IntoLua for PackageVersion {
122 fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
123 self.to_string().into_lua(lua)
124 }
125}
126
127#[derive(Error, Debug)]
128pub enum PackageVersionParseError {
129 #[error(transparent)]
130 Specrev(#[from] SpecrevParseError),
131 #[error("failed to parse version: {0}")]
132 Version(#[from] Error),
133}
134
135impl Serialize for PackageVersion {
136 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
137 where
138 S: serde::Serializer,
139 {
140 match self {
141 PackageVersion::SemVer(version) => version.serialize(serializer),
142 PackageVersion::DevVer(version) => version.serialize(serializer),
143 PackageVersion::StringVer(version) => version.serialize(serializer),
144 }
145 }
146}
147
148impl<'de> Deserialize<'de> for PackageVersion {
149 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150 where
151 D: Deserializer<'de>,
152 {
153 let s = String::deserialize(deserializer)?;
154 Self::from_str(&s).map_err(de::Error::custom)
155 }
156}
157
158impl FromLua for PackageVersion {
159 fn from_lua(
160 value: mlua::prelude::LuaValue,
161 lua: &mlua::prelude::Lua,
162 ) -> mlua::prelude::LuaResult<Self> {
163 let s = String::from_lua(value, lua)?;
164 Self::from_str(&s).map_err(|err| mlua::Error::DeserializeError(err.to_string()))
165 }
166}
167
168impl Display for PackageVersion {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 match self {
171 PackageVersion::SemVer(version) => version.fmt(f),
172 PackageVersion::DevVer(version) => version.fmt(f),
173 PackageVersion::StringVer(version) => version.fmt(f),
174 }
175 }
176}
177
178impl PartialOrd for PackageVersion {
179 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
180 Some(self.cmp(other))
181 }
182}
183
184impl Ord for PackageVersion {
185 fn cmp(&self, other: &Self) -> Ordering {
186 match (self, other) {
187 (PackageVersion::SemVer(a), PackageVersion::SemVer(b)) => a.cmp(b),
188 (PackageVersion::SemVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
189 (PackageVersion::SemVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
190 (PackageVersion::DevVer(..), PackageVersion::SemVer(..)) => Ordering::Greater,
191 (PackageVersion::DevVer(a), PackageVersion::DevVer(b)) => a.cmp(b),
192 (PackageVersion::DevVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
193 (PackageVersion::StringVer(a), PackageVersion::StringVer(b)) => a.cmp(b),
194 (PackageVersion::StringVer(..), PackageVersion::SemVer(..)) => Ordering::Less,
195 (PackageVersion::StringVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
196 }
197 }
198}
199
200impl FromStr for PackageVersion {
201 type Err = PackageVersionParseError;
202
203 fn from_str(text: &str) -> Result<Self, Self::Err> {
204 let (modrev, specrev) = split_specrev(text)?;
205 match modrev {
206 "scm" => Ok(PackageVersion::DevVer(DevVer {
207 modrev: DevVersion::Scm,
208 specrev,
209 })),
210 "dev" => Ok(PackageVersion::DevVer(DevVer {
211 modrev: DevVersion::Dev,
212 specrev,
213 })),
214 modrev => match parse_version(modrev) {
215 Ok(version) => Ok(PackageVersion::SemVer(SemVer {
216 component_count: cmp::min(text.chars().filter(|c| *c == '.').count() + 1, 3),
217 version,
218 specrev,
219 })),
220 Err(_) => Ok(PackageVersion::StringVer(StringVer {
221 modrev: modrev.into(),
222 specrev,
223 })),
224 },
225 }
226 }
227}
228
229#[derive(Clone, Eq, PartialEq, Hash, Debug)]
231pub struct SemVer {
232 version: Version,
233 component_count: usize,
234 specrev: u16,
235}
236
237impl Display for SemVer {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 let (version_str, remainder) = split_semver_version(&self.version.to_string());
240 let mut luarocks_version_str = version_str.split('.').take(self.component_count).join(".");
241 if let Some(remainder) = remainder {
242 luarocks_version_str.push_str(&format!(".{}", remainder));
246 }
247 let str = format!("{}-{}", luarocks_version_str, self.specrev);
248 str.fmt(f)
249 }
250}
251
252fn split_semver_version(version_str: &str) -> (String, Option<String>) {
253 if let Some(pos) = version_str.rfind('-') {
254 if let Some(pre_build_str) = version_str.get(pos + 1..) {
255 (version_str[..pos].into(), Some(pre_build_str.into()))
256 } else {
257 (version_str[..pos].into(), None)
258 }
259 } else {
260 (version_str.into(), None)
261 }
262}
263
264impl Serialize for SemVer {
265 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
266 where
267 S: serde::Serializer,
268 {
269 self.to_string().serialize(serializer)
270 }
271}
272
273impl Ord for SemVer {
274 fn cmp(&self, other: &Self) -> Ordering {
275 let result = self.version.cmp(&other.version);
276 if result == Ordering::Equal {
277 return self.specrev.cmp(&other.specrev);
278 }
279 result
280 }
281}
282
283impl PartialOrd for SemVer {
284 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
285 Some(self.cmp(other))
286 }
287}
288
289#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default)]
290#[serde(rename_all = "lowercase")]
291pub enum DevVersion {
292 #[default]
293 Dev,
294 Scm,
295}
296
297impl Display for DevVersion {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 match self {
300 Self::Dev => "dev".fmt(f),
301 Self::Scm => "scm".fmt(f),
302 }
303 }
304}
305
306impl IntoLua for DevVersion {
307 fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
308 self.to_string().into_lua(lua)
309 }
310}
311
312#[derive(Clone, Eq, PartialEq, Hash, Debug)]
313pub struct DevVer {
314 modrev: DevVersion,
315 specrev: u16,
316}
317
318impl Default for DevVer {
319 fn default() -> Self {
320 Self {
321 modrev: Default::default(),
322 specrev: 1,
323 }
324 }
325}
326
327impl Display for DevVer {
328 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329 let str = format!("{}-{}", self.modrev, self.specrev);
330 str.fmt(f)
331 }
332}
333
334impl Serialize for DevVer {
335 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
336 where
337 S: serde::Serializer,
338 {
339 self.to_string().serialize(serializer)
340 }
341}
342
343impl Ord for DevVer {
344 fn cmp(&self, other: &Self) -> Ordering {
345 let result = self.specrev.cmp(&other.specrev);
347 if result == Ordering::Equal {
348 return self.modrev.cmp(&other.modrev);
349 }
350 result
351 }
352}
353
354impl PartialOrd for DevVer {
355 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
356 Some(self.cmp(other))
357 }
358}
359
360#[derive(Clone, Eq, PartialEq, Hash, Debug)]
361pub struct StringVer {
362 modrev: String,
363 specrev: u16,
364}
365
366impl Display for StringVer {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 let str = format!("{}-{}", self.modrev, self.specrev);
369 str.fmt(f)
370 }
371}
372
373impl Serialize for StringVer {
374 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
375 where
376 S: serde::Serializer,
377 {
378 self.to_string().serialize(serializer)
379 }
380}
381
382impl Ord for StringVer {
383 fn cmp(&self, other: &Self) -> Ordering {
384 let result = self.specrev.cmp(&other.specrev);
386 if result == Ordering::Equal {
387 return self.modrev.cmp(&other.modrev);
388 }
389 result
390 }
391}
392
393impl PartialOrd for StringVer {
394 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
395 Some(self.cmp(other))
396 }
397}
398
399#[derive(Error, Debug)]
400#[error(transparent)]
401pub struct PackageVersionReqError(#[from] Error);
402
403#[derive(Clone, Eq, PartialEq, Hash, Debug)]
406pub enum PackageVersionReq {
407 SemVer(VersionReq),
409 DevVer(DevVersion),
411 StringVer(String),
413 Any,
415}
416
417impl FromLua for PackageVersionReq {
418 fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
419 PackageVersionReq::parse(&String::from_lua(value, lua)?).into_lua_err()
420 }
421}
422
423impl IntoLua for PackageVersionReq {
424 fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
425 let table = lua.create_table()?;
426
427 match self {
428 PackageVersionReq::SemVer(version_req) => {
429 table.set("semver", version_req.to_string())?
430 }
431 PackageVersionReq::DevVer(dev) => table.set("dev", dev)?,
432 PackageVersionReq::StringVer(dev) => table.set("stringver", dev)?,
433 PackageVersionReq::Any => table.set("any", true)?,
434 }
435
436 Ok(mlua::Value::Table(table))
437 }
438}
439
440impl PackageVersionReq {
441 pub fn any() -> Self {
443 PackageVersionReq::Any
444 }
445
446 pub fn parse(text: &str) -> Result<Self, PackageVersionReqError> {
447 PackageVersionReq::from_str(text)
448 }
449
450 pub fn matches(&self, version: &PackageVersion) -> bool {
451 match (self, version) {
452 (PackageVersionReq::SemVer(req), PackageVersion::SemVer(ver)) => {
453 req.matches(&ver.version)
454 }
455 (PackageVersionReq::DevVer(req), PackageVersion::DevVer(ver)) => req == &ver.modrev,
456 (PackageVersionReq::StringVer(req), PackageVersion::StringVer(ver)) => {
457 req == &ver.modrev
458 }
459 (PackageVersionReq::Any, _) => true,
460 _ => false,
461 }
462 }
463
464 pub fn is_any(&self) -> bool {
465 matches!(self, PackageVersionReq::Any)
466 }
467}
468
469impl Display for PackageVersionReq {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 match self {
472 PackageVersionReq::SemVer(version_req) => {
473 let mut str = version_req.to_string();
474 if str.starts_with("=") {
475 str = str.replacen("=", "==", 1);
476 } else if str.starts_with("^") {
477 str = str.replacen("^", "~>", 1);
478 }
479 str.fmt(f)
480 }
481 PackageVersionReq::DevVer(name_req) => write!(f, "=={}", &name_req),
482 PackageVersionReq::StringVer(name_req) => write!(f, "=={}", &name_req),
483 PackageVersionReq::Any => f.write_str("any"),
484 }
485 }
486}
487
488impl<'de> Deserialize<'de> for PackageVersionReq {
489 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
490 where
491 D: Deserializer<'de>,
492 {
493 String::deserialize(deserializer)?
494 .parse()
495 .map_err(serde::de::Error::custom)
496 }
497}
498
499impl FromStr for PackageVersionReq {
500 type Err = PackageVersionReqError;
501
502 fn from_str(text: &str) -> Result<Self, Self::Err> {
503 let text = correct_version_req_str(text);
504
505 let trimmed = text.trim_start_matches('=').trim_start_matches('@').trim();
506
507 match parse_version_req(&text) {
508 Ok(_) => Ok(PackageVersionReq::SemVer(parse_version_req(&text)?)),
509 Err(_) => match trimmed {
510 "scm" => Ok(PackageVersionReq::DevVer(DevVersion::Scm)),
511 "dev" => Ok(PackageVersionReq::DevVer(DevVersion::Dev)),
512 ver => Ok(PackageVersionReq::StringVer(ver.to_string())),
513 },
514 }
515 }
516}
517
518fn correct_version_req_str(text: &str) -> String {
519 text.chars()
520 .chunk_by(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
521 .into_iter()
522 .map(|(is_version_str, chars)| (is_version_str, chars.collect::<String>()))
523 .map(|(is_version_str, chunk)| {
524 if is_version_str && !is_known_dev_version_str(&chunk) {
525 let version_str = trim_specrev(&chunk);
526 correct_prerelease_version_string(version_str)
527 } else {
528 chunk
529 }
530 })
531 .collect::<String>()
532}
533
534fn trim_specrev(version_str: &str) -> &str {
535 if let Some(pos) = version_str.rfind('-') {
536 &version_str[..pos]
537 } else {
538 version_str
539 }
540}
541
542#[derive(Error, Debug)]
543pub enum SpecrevParseError {
544 #[error("specrev {specrev} in version {full_version} contains non-numeric characters")]
545 InvalidSpecrev {
546 specrev: String,
547 full_version: String,
548 },
549 #[error("could not parse specrev in version {0}")]
550 InvalidVersion(String),
551}
552
553fn split_specrev(version_str: &str) -> Result<(&str, u16), SpecrevParseError> {
554 if let Some(pos) = version_str.rfind('-') {
555 if let Some(specrev_str) = version_str.get(pos + 1..) {
556 if specrev_str.chars().all(|c| c.is_ascii_digit()) {
557 let specrev =
558 specrev_str
559 .parse::<u16>()
560 .map_err(|_| SpecrevParseError::InvalidSpecrev {
561 specrev: specrev_str.into(),
562 full_version: version_str.into(),
563 })?;
564 Ok((&version_str[..pos], specrev))
565 } else {
566 Err(SpecrevParseError::InvalidSpecrev {
567 specrev: specrev_str.into(),
568 full_version: version_str.into(),
569 })
570 }
571 } else {
572 Err(SpecrevParseError::InvalidVersion(version_str.into()))
573 }
574 } else {
575 Ok((version_str, 1))
577 }
578}
579
580fn is_known_dev_version_str(text: &str) -> bool {
581 matches!(text, "dev" | "scm")
582}
583
584fn parse_version(s: &str) -> Result<Version, Error> {
587 let version_str = correct_version_string(s);
588 Version::parse(&version_str)
589}
590
591fn parse_version_req(version_constraints: &str) -> Result<VersionReq, Error> {
593 let unescaped = decode_html_entities(version_constraints)
594 .to_string()
595 .as_str()
596 .to_owned();
597 let transformed = match unescaped {
598 s if s.starts_with("~>") => parse_pessimistic_version_constraint(s)?,
599 s if s.starts_with("@") => format!("={}", &s[1..]),
600 s if s.starts_with("==") => s[1..].to_string(),
602 s if s .find(|c: char| c.is_alphanumeric())
604 .is_some_and(|idx| idx == 0) =>
605 {
606 format!("={}", &s)
607 }
608 s => s,
609 };
610
611 let version_req = VersionReq::parse(&transformed)?;
612 Ok(version_req)
613}
614
615fn parse_pessimistic_version_constraint(version_constraint: String) -> Result<String, Error> {
616 let min_version_str = &version_constraint[2..].trim();
618 let min_version = Version::parse(&correct_version_string(min_version_str))?;
619
620 let max_version = match min_version_str.matches('.').count() {
621 0 => Version {
622 major: &min_version.major + 1,
623 ..min_version.clone()
624 },
625 1 => Version {
626 minor: &min_version.minor + 1,
627 ..min_version.clone()
628 },
629 _ => Version {
630 patch: &min_version.patch + 1,
631 ..min_version.clone()
632 },
633 };
634
635 Ok(format!(">= {min_version}, < {max_version}"))
636}
637
638fn correct_version_string(version: &str) -> String {
643 let version = append_minor_patch_if_missing(version);
644 correct_prerelease_version_string(&version)
645}
646
647fn correct_prerelease_version_string(version: &str) -> String {
648 let parts: Vec<&str> = version.split('.').collect();
649 if parts.len() > 3 {
650 let corrected_version = format!(
651 "{}.{}.{}-{}",
652 parts[0],
653 parts[1],
654 parts[2],
655 parts[3..].join(".")
656 );
657 corrected_version
658 } else {
659 version.to_string()
660 }
661}
662
663fn append_minor_patch_if_missing(version: &str) -> String {
665 if version.matches('.').count() < 2 {
666 append_minor_patch_if_missing(&format!("{}.0", version))
667 } else {
668 version.to_string()
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675
676 #[tokio::test]
677 async fn parse_semver_version() {
678 assert_eq!(
679 PackageVersion::parse("1-1").unwrap(),
680 PackageVersion::SemVer(SemVer {
681 version: "1.0.0".parse().unwrap(),
682 component_count: 1,
683 specrev: 1,
684 })
685 );
686 assert_eq!(
687 PackageVersion::parse("1.0-1").unwrap(),
688 PackageVersion::SemVer(SemVer {
689 version: "1.0.0".parse().unwrap(),
690 component_count: 2,
691 specrev: 1,
692 })
693 );
694 assert_eq!(
695 PackageVersion::parse("1.0.0-1").unwrap(),
696 PackageVersion::SemVer(SemVer {
697 version: "1.0.0".parse().unwrap(),
698 component_count: 3,
699 specrev: 1
700 })
701 );
702 assert_eq!(
703 PackageVersion::parse("1.0.0-1").unwrap(),
704 PackageVersion::SemVer(SemVer {
705 version: "1.0.0".parse().unwrap(),
706 component_count: 3,
707 specrev: 1
708 })
709 );
710 assert_eq!(
711 PackageVersion::parse("1.0.0-10-1").unwrap(),
712 PackageVersion::SemVer(SemVer {
713 version: "1.0.0-10".parse().unwrap(),
714 component_count: 3,
715 specrev: 1
716 })
717 );
718 assert_eq!(
719 PackageVersion::parse("1.0.0.10-1").unwrap(),
720 PackageVersion::SemVer(SemVer {
721 version: "1.0.0-10".parse().unwrap(),
722 component_count: 3,
723 specrev: 1
724 })
725 );
726 assert_eq!(
727 PackageVersion::parse("1.0.0.10.0-1").unwrap(),
728 PackageVersion::SemVer(SemVer {
729 version: "1.0.0-10.0".parse().unwrap(),
730 component_count: 3,
731 specrev: 1
732 })
733 );
734 }
735
736 #[tokio::test]
737 async fn parse_dev_version() {
738 assert_eq!(
739 PackageVersion::parse("dev-1").unwrap(),
740 PackageVersion::DevVer(DevVer {
741 modrev: DevVersion::Dev,
742 specrev: 1
743 })
744 );
745 assert_eq!(
746 PackageVersion::parse("scm-1").unwrap(),
747 PackageVersion::DevVer(DevVer {
748 modrev: DevVersion::Scm,
749 specrev: 1
750 })
751 );
752 assert_eq!(
753 PackageVersion::parse("git-1").unwrap(),
754 PackageVersion::StringVer(StringVer {
755 modrev: "git".into(),
756 specrev: 1
757 })
758 );
759 assert_eq!(
760 PackageVersion::parse("scm-1").unwrap(),
761 PackageVersion::DevVer(DevVer {
762 modrev: DevVersion::Scm,
763 specrev: 1
764 })
765 );
766 }
767
768 #[tokio::test]
769 async fn parse_dev_version_req() {
770 assert_eq!(
771 PackageVersionReq::parse("dev").unwrap(),
772 PackageVersionReq::DevVer(DevVersion::Dev)
773 );
774 assert_eq!(
775 PackageVersionReq::parse("scm").unwrap(),
776 PackageVersionReq::DevVer(DevVersion::Scm)
777 );
778 assert_eq!(
779 PackageVersionReq::parse("git").unwrap(),
780 PackageVersionReq::StringVer("git".into())
781 );
782 assert_eq!(
783 PackageVersionReq::parse("==dev").unwrap(),
784 PackageVersionReq::DevVer(DevVersion::Dev)
785 );
786 assert_eq!(
787 PackageVersionReq::parse("==git").unwrap(),
788 PackageVersionReq::StringVer("git".into())
789 );
790 assert_eq!(
791 PackageVersionReq::parse("== dev").unwrap(),
792 PackageVersionReq::DevVer(DevVersion::Dev)
793 );
794 assert_eq!(
795 PackageVersionReq::parse("== scm").unwrap(),
796 PackageVersionReq::DevVer(DevVersion::Scm)
797 );
798 assert_eq!(
799 PackageVersionReq::parse("@dev").unwrap(),
800 PackageVersionReq::DevVer(DevVersion::Dev)
801 );
802 assert_eq!(
803 PackageVersionReq::parse("@git").unwrap(),
804 PackageVersionReq::StringVer("git".into())
805 );
806 assert_eq!(
807 PackageVersionReq::parse("@ dev").unwrap(),
808 PackageVersionReq::DevVer(DevVersion::Dev)
809 );
810 assert_eq!(
811 PackageVersionReq::parse("@ scm").unwrap(),
812 PackageVersionReq::DevVer(DevVersion::Scm)
813 );
814 assert_eq!(
815 PackageVersionReq::parse(">1-1,<1.2-2").unwrap(),
816 PackageVersionReq::SemVer(">1,<1.2".parse().unwrap())
817 );
818 assert_eq!(
819 PackageVersionReq::parse("> 1-1, < 1.2-2").unwrap(),
820 PackageVersionReq::SemVer("> 1, < 1.2".parse().unwrap())
821 );
822 assert_eq!(
823 PackageVersionReq::parse("> 2.1.0.10, < 2.1.1").unwrap(),
824 PackageVersionReq::SemVer("> 2.1.0-10, < 2.1.1".parse().unwrap())
825 );
826 }
827
828 #[tokio::test]
829 async fn package_version_req_semver_roundtrips() {
830 let req = PackageVersionReq::parse("==0.7.1").unwrap();
831 assert_eq!(req.to_string(), "==0.7.1");
832
833 let req = PackageVersionReq::parse("0.7.1").unwrap();
834 assert_eq!(req.to_string(), "==0.7.1");
835
836 let req = PackageVersionReq::parse(">=0.7.1").unwrap();
837 assert_eq!(req.to_string(), ">=0.7.1");
838
839 let req = PackageVersionReq::parse(">0.7.1").unwrap();
840 assert_eq!(req.to_string(), ">0.7.1");
841
842 let req = PackageVersionReq::parse("<0.7.1").unwrap();
843 assert_eq!(req.to_string(), "<0.7.1");
844
845 let req = PackageVersionReq::parse("~> 0.7.1").unwrap();
846 assert_eq!(req.to_string(), ">=0.7.1, <0.7.2");
847 }
848
849 #[tokio::test]
850 async fn package_version_req_devver_roundtrips() {
851 let req = PackageVersionReq::parse("==scm").unwrap();
852 assert_eq!(req.to_string(), "==scm");
853
854 let req = PackageVersionReq::parse("@scm").unwrap();
855 assert_eq!(req.to_string(), "==scm");
856
857 let req = PackageVersionReq::parse("scm").unwrap();
858 assert_eq!(req.to_string(), "==scm");
859
860 let req = PackageVersionReq::parse("==a144124839f027a2d0a95791936c478d047126fc").unwrap();
861 assert_eq!(
862 req.to_string(),
863 "==a144124839f027a2d0a95791936c478d047126fc"
864 );
865 }
866}