1use std::{cmp, env, fmt, str};
17
18pub fn crate_version() -> Version {
19 let major = env!("CARGO_PKG_VERSION_MAJOR").parse::<u16>().unwrap();
20 let minor = env!("CARGO_PKG_VERSION_MINOR").parse::<u16>().unwrap();
21 let patch = env!("CARGO_PKG_VERSION_PATCH").parse::<u16>().unwrap();
22 Version::MajorMinorPatch(major, minor, patch)
23}
24
25#[derive(Clone, Debug)]
26pub enum Version {
27 Major(u16),
28 MajorMinor(u16, u16),
29 MajorMinorPatch(u16, u16, u16),
30}
31
32impl Version {
33 fn to_triplet(&self) -> (u16, u16, u16) {
34 match self {
35 Version::Major(a) => (*a, 0, 0),
36 Version::MajorMinor(a, b) => (*a, *b, 0),
37 Version::MajorMinorPatch(a, b, c) => (*a, *b, *c),
38 }
39 }
40
41 pub fn next_breaking(&self) -> Self {
42 use Version::*;
43 match self {
44 Major(0) => MajorMinorPatch(0, 1, 0),
45 Major(a) => MajorMinorPatch(a + 1, 0, 0),
46 MajorMinor(0, b) => MajorMinorPatch(0, b + 1, 0),
47 MajorMinor(a, _) => MajorMinorPatch(a + 1, 0, 0),
48 MajorMinorPatch(0, b, _) => MajorMinorPatch(0, b + 1, 0),
49 MajorMinorPatch(a, _, _) => MajorMinorPatch(a + 1, 0, 0),
50 }
51 }
52
53 pub fn cmp_to_range(&self, range: &VersionRange) -> cmp::Ordering {
54 use cmp::Ordering::*;
55 if let Some(ref start) = range.start {
56 if *self < *start {
57 return Less;
58 }
59 }
60 if let Some(ref end) = range.end {
61 if *end < *self || (!range.inclusive && *end == *self) {
62 return Greater;
63 }
64 }
65 Equal
66 }
67}
68
69impl cmp::PartialEq for Version {
70 fn eq(&self, rhs: &Self) -> bool {
71 self.to_triplet() == rhs.to_triplet()
72 }
73}
74
75impl cmp::PartialOrd for Version {
76 fn partial_cmp(&self, rhs: &Self) -> Option<cmp::Ordering> {
77 self.to_triplet().partial_cmp(&rhs.to_triplet())
78 }
79}
80
81impl str::FromStr for Version {
82 type Err = ParseVersionError;
83
84 fn from_str(s: &str) -> Result<Self, Self::Err> {
85 let mut it = s.split('.');
86 let Some(major_str) = it.next() else {
87 return Err(ParseVersionError)
88 };
89 let major = major_str.parse::<u16>().map_err(|_| ParseVersionError)?;
90 let Some(minor_str) = it.next() else {
91 return Ok(Version::Major(major));
92 };
93 let minor = minor_str.parse::<u16>().map_err(|_| ParseVersionError)?;
94 let Some(patch_str) = it.next() else {
95 return Ok(Version::MajorMinor(major, minor))
96 };
97 let patch = patch_str.parse::<u16>().map_err(|_| ParseVersionError)?;
98 if it.next().is_none() {
99 Ok(Version::MajorMinorPatch(major, minor, patch))
100 } else {
101 Err(ParseVersionError)
102 }
103 }
104}
105
106#[derive(Clone, Copy, Debug, PartialEq)]
107pub struct ParseVersionError;
108
109impl fmt::Display for Version {
110 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111 use Version::*;
112 match self {
113 Major(a) => write!(f, "{}.0.0", a),
114 MajorMinor(a, b) => write!(f, "{}.{}.0", a, b),
115 MajorMinorPatch(a, b, c) => write!(f, "{}.{}.{}", a, b, c),
116 }
117 }
118}
119
120#[derive(Clone, Debug)]
121pub struct VersionRange {
122 pub start: Option<Version>,
123 pub end: Option<Version>,
124 pub inclusive: bool,
125}
126
127impl VersionRange {
128 pub fn non_breaking_from(start: &Version) -> Self {
129 Self {
130 start: Some(start.clone()),
131 end: Some(start.next_breaking()),
132 inclusive: false,
133 }
134 }
135}
136
137impl str::FromStr for VersionRange {
138 type Err = ParseVersionRangeError;
139
140 fn from_str(s: &str) -> Result<Self, Self::Err> {
141 fn parse_version(
142 s: &str,
143 ) -> Result<Option<Version>, ParseVersionRangeError> {
144 match s.parse() {
145 Ok(v) => Ok(Some(v)),
146 Err(_) => Err(ParseVersionRangeError),
147 }
148 }
149
150 fn parse_opt_version(
151 s: &str,
152 ) -> Result<Option<Version>, ParseVersionRangeError> {
153 if s.is_empty() {
154 Ok(None)
155 } else {
156 parse_version(s)
157 }
158 }
159
160 if let Some((start_str, end_str)) = s.split_once("..=") {
161 let start = parse_opt_version(start_str)?;
162 let end = parse_version(end_str)?;
163 if start.is_some() && end.is_some() && start > end {
164 Err(ParseVersionRangeError)
165 } else {
166 Ok(Self {
167 start,
168 end,
169 inclusive: true,
170 })
171 }
172 } else if let Some((start_str, end_str)) = s.split_once("..") {
173 let start = parse_opt_version(start_str)?;
174 let end = parse_opt_version(end_str)?;
175 if start.is_some() && end.is_some() && start >= end {
176 Err(ParseVersionRangeError)
177 } else {
178 Ok(Self {
179 start,
180 end,
181 inclusive: false,
182 })
183 }
184 } else {
185 Err(ParseVersionRangeError)
186 }
187 }
188}
189
190#[derive(Clone, Copy, Debug, PartialEq)]
191pub struct ParseVersionRangeError;
192
193impl fmt::Display for VersionRange {
194 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195 if let Some(ref v) = self.start {
196 write!(f, "{}", v)?;
197 }
198 write!(f, "{}", if self.inclusive { "..=" } else { ".." })?;
199 if let Some(ref v) = self.end {
200 write!(f, "{}", v)?;
201 }
202 Ok(())
203 }
204}
205
206#[cfg(test)]
207mod test {
208
209 use super::*;
210
211 #[test]
212 fn version_equivalence() {
213 use Version::*;
214
215 assert_eq!(Major(1), Major(1));
216 assert_eq!(Major(1), MajorMinor(1, 0));
217 assert_eq!(Major(1), MajorMinorPatch(1, 0, 0));
218
219 assert_eq!(MajorMinor(1, 2), MajorMinor(1, 2));
220 assert_eq!(MajorMinor(1, 2), MajorMinorPatch(1, 2, 0));
221
222 assert_eq!(MajorMinorPatch(1, 2, 3), MajorMinorPatch(1, 2, 3));
223 }
224
225 #[test]
226 fn version_ordering() {
227 use Version::*;
228
229 assert!(Major(1) < Major(2));
230 assert!(Major(2) > Major(1));
231
232 assert!(MajorMinor(1, 2) < MajorMinor(1, 3));
233 assert!(MajorMinor(1, 3) > MajorMinor(1, 2));
234
235 assert!(MajorMinor(1, 2) < MajorMinor(2, 1));
236 assert!(MajorMinor(2, 1) > MajorMinor(1, 2));
237
238 assert!(Major(1) < MajorMinor(1, 2));
239 assert!(MajorMinor(1, 2) < Major(2));
240
241 assert!(MajorMinor(1, 2) > Major(1));
242 assert!(Major(2) > MajorMinor(1, 2));
243
244 assert!(MajorMinorPatch(1, 2, 3) < MajorMinorPatch(1, 2, 4));
245 assert!(MajorMinorPatch(1, 2, 4) > MajorMinorPatch(1, 2, 3));
246
247 assert!(MajorMinor(1, 2) < MajorMinorPatch(1, 2, 3));
248 assert!(Major(1) < MajorMinorPatch(1, 2, 3));
249 assert!(MajorMinorPatch(1, 2, 3) < MajorMinor(1, 3));
250 assert!(MajorMinorPatch(1, 2, 3) < Major(2));
251
252 assert!(MajorMinorPatch(1, 2, 3) > MajorMinor(1, 2));
253 assert!(MajorMinorPatch(1, 2, 3) > Major(1));
254 assert!(MajorMinor(1, 3) > MajorMinorPatch(1, 2, 3));
255 assert!(Major(2) > MajorMinorPatch(1, 2, 3));
256 }
257
258 #[test]
259 fn parse_good_version_strings() {
260 use Version::*;
261
262 assert!(match "1".parse::<Version>() {
263 Ok(Major(1)) => true,
264 _ => false,
265 });
266 assert!(match "1.2".parse::<Version>() {
267 Ok(MajorMinor(1, 2)) => true,
268 _ => false,
269 });
270 assert!(match "1.2.3".parse::<Version>() {
271 Ok(MajorMinorPatch(1, 2, 3)) => true,
272 _ => false,
273 });
274 assert!(match "123.456.789".parse::<Version>() {
275 Ok(MajorMinorPatch(123, 456, 789)) => true,
276 _ => false,
277 });
278 assert!(match "0.0.0".parse::<Version>() {
279 Ok(MajorMinorPatch(0, 0, 0)) => true,
280 _ => false,
281 });
282 }
283
284 #[test]
285 fn try_parsing_bad_version_strings() {
286 assert_eq!("".parse::<Version>(), Err(ParseVersionError));
287 assert_eq!("1 ".parse::<Version>(), Err(ParseVersionError));
288 assert_eq!(" 1".parse::<Version>(), Err(ParseVersionError));
289 assert_eq!(" ".parse::<Version>(), Err(ParseVersionError));
290 assert_eq!("1.".parse::<Version>(), Err(ParseVersionError));
291 assert_eq!("1.".parse::<Version>(), Err(ParseVersionError));
292 assert_eq!("1.2.".parse::<Version>(), Err(ParseVersionError));
293 assert_eq!("1.2.3.".parse::<Version>(), Err(ParseVersionError));
294 assert_eq!("1.2.3.4".parse::<Version>(), Err(ParseVersionError));
295 assert_eq!("whatever".parse::<Version>(), Err(ParseVersionError));
296 }
297
298 #[test]
299 fn display_version_string() {
300 use Version::*;
301
302 assert_eq!(format!("{}", Major(123)), "123.0.0");
303 assert_eq!(format!("{}", MajorMinor(123, 456)), "123.456.0");
304 assert_eq!(format!("{}", MajorMinorPatch(123, 456, 789)), "123.456.789");
305 }
306
307 #[test]
308 fn parse_good_version_ranges() {
309 use Version::*;
310
311 assert!(match "1..=1".parse() {
312 Ok(VersionRange {
313 start: Some(Major(1)),
314 end: Some(Major(1)),
315 inclusive: true,
316 }) => true,
317 bad => panic!("bad parse: {:#?}", bad),
318 });
319
320 assert!(match "1..=1.0.0".parse() {
321 Ok(VersionRange {
322 start: Some(Major(1)),
323 end: Some(MajorMinorPatch(1, 0, 0)),
324 inclusive: true,
325 }) => true,
326 bad => panic!("bad parse: {:#?}", bad),
327 });
328
329 assert!(match "1..1.0.1".parse() {
330 Ok(VersionRange {
331 start: Some(Major(1)),
332 end: Some(MajorMinorPatch(1, 0, 1)),
333 inclusive: false,
334 }) => true,
335 bad => panic!("bad parse: {:#?}", bad),
336 });
337
338 assert!(match "1..".parse() {
339 Ok(VersionRange {
340 start: Some(Major(1)),
341 end: None,
342 inclusive: false,
343 }) => true,
344 bad => panic!("bad parse: {:#?}", bad),
345 });
346
347 assert!(match "..1".parse() {
348 Ok(VersionRange {
349 start: None,
350 end: Some(Major(1)),
351 inclusive: false,
352 }) => true,
353 bad => panic!("bad parse: {:#?}", bad),
354 });
355
356 assert!(match "..=1".parse() {
357 Ok(VersionRange {
358 start: None,
359 end: Some(Major(1)),
360 inclusive: true,
361 }) => true,
362 bad => panic!("bad parse: {:#?}", bad),
363 });
364
365 assert!(match "..".parse() {
366 Ok(VersionRange {
367 start: None,
368 end: None,
369 inclusive: false,
370 }) => true,
371 bad => panic!("bad parse: {:#?}", bad),
372 });
373 }
374
375 #[test]
376 fn try_parsing_bad_version_ranges() {
377 assert_eq!(
378 "1..1".parse::<VersionRange>().unwrap_err(),
379 ParseVersionRangeError
380 );
381 assert_eq!(
382 "1.2.3..1.2.2".parse::<VersionRange>().unwrap_err(),
383 ParseVersionRangeError
384 );
385 assert_eq!(
386 "1.2.3..=1.2.2".parse::<VersionRange>().unwrap_err(),
387 ParseVersionRangeError
388 );
389 assert_eq!(
390 "..=".parse::<VersionRange>().unwrap_err(),
391 ParseVersionRangeError
392 );
393 }
394
395 #[test]
396 fn compare_version_against_range() {
397 use cmp::Ordering::*;
398
399 let point: VersionRange = "1.2.3..=1.2.3".parse().unwrap();
400
401 assert_eq!(
402 "1.2.2".parse::<Version>().unwrap().cmp_to_range(&point),
403 Less
404 );
405 assert_eq!(
406 "1.2.3".parse::<Version>().unwrap().cmp_to_range(&point),
407 Equal
408 );
409 assert_eq!(
410 "1.2.4".parse::<Version>().unwrap().cmp_to_range(&point),
411 Greater
412 );
413
414 let open: VersionRange = "1..2".parse().unwrap();
415
416 assert_eq!(
417 "0.65535.65535"
418 .parse::<Version>()
419 .unwrap()
420 .cmp_to_range(&open),
421 Less
422 );
423 assert_eq!(
424 "1.0.0".parse::<Version>().unwrap().cmp_to_range(&open),
425 Equal
426 );
427 assert_eq!(
428 "1.65535.65535"
429 .parse::<Version>()
430 .unwrap()
431 .cmp_to_range(&open),
432 Equal
433 );
434 assert_eq!(
435 "2.0.0".parse::<Version>().unwrap().cmp_to_range(&open),
436 Greater
437 );
438 assert_eq!(
439 "2.0.1".parse::<Version>().unwrap().cmp_to_range(&open),
440 Greater
441 );
442
443 let closed: VersionRange = "1..=2".parse().unwrap();
444
445 assert_eq!(
446 "0.65535.65535"
447 .parse::<Version>()
448 .unwrap()
449 .cmp_to_range(&closed),
450 Less
451 );
452 assert_eq!(
453 "1.0.0".parse::<Version>().unwrap().cmp_to_range(&closed),
454 Equal
455 );
456 assert_eq!(
457 "1.65535.65535"
458 .parse::<Version>()
459 .unwrap()
460 .cmp_to_range(&closed),
461 Equal
462 );
463 assert_eq!(
464 "2.0.0".parse::<Version>().unwrap().cmp_to_range(&closed),
465 Equal
466 );
467 assert_eq!(
468 "2.0.1".parse::<Version>().unwrap().cmp_to_range(&closed),
469 Greater
470 );
471 }
472}