Skip to content

Commit 0331a9e

Browse files
authored
Merge pull request #10 from mxcl/fmt_display
Conform Semver and Range to fmt::Display
2 parents 2f522dd + dd18064 commit 0331a9e

File tree

7 files changed

+256
-12
lines changed

7 files changed

+256
-12
lines changed

.justfile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,16 @@ default:
44

55
# Generate coverage/lcov.info
66
coverage:
7-
cargo tarpaulin --engine ptrace -o lcov --output-dir coverage
7+
cargo tarpaulin --engine ptrace -o lcov --output-dir coverage
8+
9+
# For getting ptrace as html on macos
10+
docker-coverage:
11+
docker image pull pkgxdev/pkgx
12+
docker run \
13+
--name semverator \
14+
--rm \
15+
--volume .:/volume \
16+
--security-opt seccomp=unconfined \
17+
--platform linux/amd64 \
18+
xd009642/tarpaulin \
19+
cargo tarpaulin --engine llvm -o html --all --all-targets --all-features --output-dir /volume/coverage

lib/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ crate-type = ["cdylib", "rlib"]
1717
anyhow = "1.0.75"
1818
lazy_static = "1.5.0"
1919
regex = "1.9.5"
20+
21+
[lints.rust]
22+
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }

lib/src/range/mod.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use crate::semver::Semver;
2-
use std::hash::{Hash, Hasher};
2+
use std::{
3+
fmt,
4+
hash::{Hash, Hasher},
5+
};
36

47
pub mod intersect;
58
pub mod max;
@@ -39,7 +42,79 @@ impl Range {
3942
}
4043

4144
impl Hash for Semver {
45+
#[cfg(not(tarpaulin_include))]
4246
fn hash<H: Hasher>(&self, state: &mut H) {
4347
self.components.hash(state);
4448
}
4549
}
50+
51+
impl fmt::Display for Range {
52+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53+
let str = self
54+
.set
55+
.iter()
56+
.map(|v| match v {
57+
Constraint::Any => "*".to_string(),
58+
Constraint::Single(v) => format!("={}", v),
59+
Constraint::Contiguous(v1, v2) => {
60+
let v1_chomp = v1.raw.trim_end_matches(".0").to_string();
61+
let v2_chomp = v2.raw.trim_end_matches(".0").to_string();
62+
if v2.major == v1.major + 1 && v2.minor == 0 && v2.patch == 0 {
63+
if v1.major == 0 {
64+
if v1.components.len() == 1 {
65+
"^0".to_string()
66+
} else {
67+
format!(">={}<1", v1_chomp)
68+
}
69+
} else {
70+
format!("^{}", v1_chomp)
71+
}
72+
} else if v2.major == v1.major && v2.minor == v1.minor + 1 && v2.patch == 0 {
73+
format!("~{}", v1_chomp)
74+
} else if v2.major == usize::MAX {
75+
format!(">={}", v1_chomp)
76+
} else if at(&v1.clone(), &v2.clone()) {
77+
format!("@{}", v1)
78+
} else {
79+
format!(">={}<{}", v1_chomp, v2_chomp)
80+
}
81+
}
82+
})
83+
.collect::<Vec<_>>()
84+
.join(",");
85+
write!(f, "{}", str)
86+
}
87+
}
88+
89+
/// checks @ syntax, eg. node@22.1
90+
/// `@` is `=`, as long as there's 3 components
91+
fn at(left: &Semver, right: &Semver) -> bool {
92+
let mut cc1 = left.components.clone();
93+
let cc2 = &right.components;
94+
95+
// helper function to get the last element of a slice
96+
fn last(arr: &[usize]) -> usize {
97+
*arr.last().unwrap()
98+
}
99+
100+
if cc1.len() > cc2.len() {
101+
return false;
102+
}
103+
104+
// Ensure cc1 and cc2 have the same length by appending 0s to cc1
105+
while cc1.len() < cc2.len() {
106+
cc1.push(0);
107+
}
108+
109+
if last(&cc1) + 1 != last(cc2) {
110+
return false;
111+
}
112+
113+
for i in 0..cc1.len() - 1 {
114+
if cc1[i] != cc2[i] {
115+
return false;
116+
}
117+
}
118+
119+
true
120+
}

lib/src/range/parse.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ lazy_static! {
99
static ref RANGE_REGEX: Regex = Regex::new(r"\s*(,|\|\|)\s*").unwrap();
1010
static ref CONSTRAINT_REGEX_RANGE: Regex =
1111
Regex::new(r"^>=((\d+\.)*\d+)\s*(<((\d+\.)*\d+))?$").unwrap();
12-
static ref CONSTRAINT_REGEX_SIMPLE: Regex = Regex::new(r"^([~=<^])(.+)$").unwrap();
12+
static ref CONSTRAINT_REGEX_SIMPLE: Regex = Regex::new(r"^([~=<^@])(.+)$").unwrap();
1313
}
1414

1515
impl Range {
@@ -91,6 +91,20 @@ impl Constraint {
9191
let v2 = Semver::parse(cap.get(2).context("invalid description")?.as_str())?;
9292
Ok(Constraint::Contiguous(v1, v2))
9393
}
94+
"@" => {
95+
let v1 = Semver::parse(cap.get(2).context("invalid description")?.as_str())?;
96+
let mut parts = v1.components.clone();
97+
let last = parts.last_mut().context("version too short")?;
98+
*last += 1;
99+
let v2 = Semver::parse(
100+
&parts
101+
.iter()
102+
.map(|c| c.to_string())
103+
.collect::<Vec<_>>()
104+
.join("."),
105+
)?;
106+
Ok(Constraint::Contiguous(v1, v2))
107+
}
94108
"=" => Ok(Constraint::Single(Semver::parse(
95109
cap.get(2).context("invalid description")?.as_str(),
96110
)?)),

lib/src/semver/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::fmt;
2+
13
pub mod bump;
24
pub mod compare;
35
pub mod parse;
@@ -28,3 +30,40 @@ impl Semver {
2830
}
2931
}
3032
}
33+
34+
impl fmt::Display for Semver {
35+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36+
write!(
37+
f,
38+
"{}",
39+
self.components
40+
.iter()
41+
.map(|c| c.to_string())
42+
.collect::<Vec<_>>()
43+
.join(".")
44+
)?;
45+
if !self.prerelease.is_empty() {
46+
write!(
47+
f,
48+
"-{}",
49+
self.prerelease
50+
.iter()
51+
.map(|c| c.to_string())
52+
.collect::<Vec<_>>()
53+
.join(".")
54+
)?;
55+
}
56+
if !self.build.is_empty() {
57+
write!(
58+
f,
59+
"+{}",
60+
self.build
61+
.iter()
62+
.map(|c| c.to_string())
63+
.collect::<Vec<_>>()
64+
.join(".")
65+
)?;
66+
}
67+
Ok(())
68+
}
69+
}

lib/src/tests/range.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ fn test_parse() -> Result<()> {
1616
let j = Range::parse("Your mom");
1717
let k = Range::parse("");
1818
let l = Range::parse(">=12");
19+
let m = Range::parse("@1");
20+
let n = Range::parse("@1.1");
21+
let o = Range::parse("@1.1.1");
22+
let p = Range::parse("@1.1.1.1");
1923

2024
assert!(a.is_ok());
2125
assert!(b.is_ok());
@@ -29,6 +33,10 @@ fn test_parse() -> Result<()> {
2933
assert!(j.is_err());
3034
assert!(k.is_err());
3135
assert!(l.is_ok());
36+
assert!(m.is_ok());
37+
assert!(n.is_ok());
38+
assert!(o.is_ok());
39+
assert!(p.is_ok());
3240

3341
assert_eq!(f?.set.len(), 5);
3442

@@ -203,3 +211,71 @@ fn test_intersect() -> Result<()> {
203211

204212
Ok(())
205213
}
214+
215+
#[test]
216+
fn test_at() -> Result<()> {
217+
let ra = Range::parse(">=1.0<1.1")?;
218+
let rb = Range::parse("=1.1")?;
219+
220+
assert_eq!(format!("{ra}"), "~1");
221+
assert_eq!(format!("{rb}"), "=1.1");
222+
223+
let rc = Range::parse(">=1.1.0<1.1.1")?;
224+
let rd = Range::parse("=1.1.1")?;
225+
226+
assert_eq!(format!("{rc}"), "@1.1.0");
227+
assert_eq!(format!("{rd}"), "=1.1.1");
228+
229+
let re = Range::parse(">=1.1.1.0<1.1.1.1")?;
230+
let rf = Range::parse("=1.1.1.0")?;
231+
232+
assert_eq!(format!("{re}"), "@1.1.1.0");
233+
assert_eq!(format!("{rf}"), "=1.1.1.0");
234+
235+
let rg = Range::parse(">=1.1<1.1.1.1.1")?;
236+
let rh = Range::parse(">=1.1.1<1.1.3")?;
237+
let ri = Range::parse(">=1.1.1<1.2.2")?;
238+
239+
assert_eq!(format!("{rg}"), ">=1.1<1.1.1.1.1");
240+
assert_eq!(format!("{rh}"), ">=1.1.1<1.1.3");
241+
assert_eq!(format!("{ri}"), ">=1.1.1<1.2.2");
242+
243+
let rj = Range::parse("@1")?;
244+
let rk = Range::parse("@1.1")?;
245+
let rl = Range::parse("@1.1.1")?;
246+
let rm = Range::parse("@1.1.1.1")?;
247+
248+
assert_eq!(format!("{rj}"), "^1");
249+
assert_eq!(format!("{rk}"), "~1.1");
250+
assert_eq!(format!("{rl}"), "@1.1.1");
251+
assert_eq!(format!("{rm}"), "@1.1.1.1");
252+
253+
Ok(())
254+
}
255+
256+
#[test]
257+
fn test_display() -> Result<()> {
258+
let ra = Range::parse("^3.7")?;
259+
let rb = Range::parse("=3.11")?;
260+
let rc = Range::parse("^3.9")?;
261+
let rd = Range::parse("*")?;
262+
let re = Range::parse(">=0<1")?;
263+
let rf = Range::parse(">=0.1<1")?;
264+
let rg = Range::parse(">=0.1<0.2")?;
265+
let rh = Range::parse(">=0.1.1<0.2")?;
266+
let ri = Range::parse(">=0.1.1")?;
267+
let rj = Range::parse(">=0.1.1<3")?;
268+
269+
assert_eq!(ra.to_string(), "^3.7");
270+
assert_eq!(rb.to_string(), "=3.11");
271+
assert_eq!(rc.to_string(), "^3.9");
272+
assert_eq!(rd.to_string(), "*");
273+
assert_eq!(re.to_string(), "^0");
274+
assert_eq!(rf.to_string(), ">=0.1<1");
275+
assert_eq!(rg.to_string(), "~0.1");
276+
assert_eq!(rh.to_string(), "~0.1.1");
277+
assert_eq!(ri.to_string(), ">=0.1.1");
278+
assert_eq!(rj.to_string(), ">=0.1.1<3");
279+
280+
Ok(())
281+
}

lib/src/tests/semver.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,20 @@ fn test_compare() -> Result<()> {
8181
let j = Semver::parse("1.2.3-alpha.1+7ec0834")?;
8282
let k = Semver::parse("1.2.3-alpha.2+7ec0834")?;
8383

84-
assert!(e.lt(&f));
85-
assert!(f.eq(&f));
84+
assert!(e.lt(&f)); // 1.2.3-alpha < 1.2.3-alpha.1
85+
assert!(f.eq(&f)); // 1.2.3-alpha.1 == 1.2.3-alpha.1
8686
assert!(f.lt(&a)); // 1.2.3-alpha.1 < 1.2.3
87-
assert!(f.lt(&g));
88-
assert!(f.lt(&g));
89-
assert!(g.lt(&h));
90-
assert!(f.lt(&i));
91-
assert!(i.gt(&j));
92-
assert!(i.lt(&k));
93-
assert!(j.lt(&k));
87+
assert!(a.gt(&f)); // 1.2.3 > 1.2.3-alpha.1
88+
assert!(f.lt(&g)); // 1.2.3-alpha.1 < 1.2.3-alpha.2
89+
assert!(g.gt(&f)); // 1.2.3-alpha.2 > 1.2.3-alpha.1
90+
assert!(g.lt(&h)); // 1.2.3-alpha.2 < 1.2.3-beta.1
91+
assert!(f.lt(&i)); // 1.2.3-alpha.1 < 1.2.3-alpha.1+8ec0834
92+
assert!(i.gt(&f)); // 1.2.3-alpha.1+8ec0834 > 1.2.3-alpha.1
93+
assert!(i.eq(&i)); // 1.2.3-alpha.1+8ec0834 == 1.2.3-alpha.1+8ec0834
94+
assert!(i.gt(&j)); // 1.2.3-alpha.1+8ec0834 > 1.2.3-alpha.1+7ec0834
95+
assert!(j.lt(&i)); // 1.2.3-alpha.1+7ec0834 < 1.2.3-alpha.1+8ec0834
96+
assert!(i.lt(&k)); // 1.2.3-alpha.1+8ec0834 < 1.2.3-alpha.2+7ec0834
97+
assert!(j.lt(&k)); // 1.2.3-alpha.1+7ec0834 < 1.2.3-alpha.2+7ec0834
9498

9599
Ok(())
96100
}
@@ -156,3 +160,24 @@ fn test_infinty() {
156160
assert_eq!(inf.components, [usize::MAX, usize::MAX, usize::MAX]);
157161
assert_eq!(inf.raw, "Infinity.Infinity.Infinity");
158162
}
163+
164+
#[test]
165+
fn test_display() -> Result<()> {
166+
let a = Semver::parse("1.2.3")?;
167+
let b = Semver::parse("1.2.3-alpha")?;
168+
let c = Semver::parse("1.2.0")?;
169+
let d = Semver::parse("1.0.0")?;
170+
let e = Semver::parse("1.2.3-alpha.1+b40")?;
171+
let f = Semver::parse("1.2.3-alpha.1+build.40")?;
172+
let g = Semver::parse("1")?;
173+
174+
assert_eq!(a.to_string(), "1.2.3");
175+
assert_eq!(b.to_string(), "1.2.3-alpha");
176+
assert_eq!(c.to_string(), "1.2.0");
177+
assert_eq!(d.to_string(), "1.0.0");
178+
assert_eq!(e.to_string(), "1.2.3-alpha.1+b40");
179+
assert_eq!(f.to_string(), "1.2.3-alpha.1+build.40");
180+
assert_eq!(g.to_string(), "1");
181+
182+
Ok(())
183+
}

0 commit comments

Comments
 (0)