Skip to main content

verlib/
lib.rs

1//! Version manipulation library.
2//!
3//! This library provides a way to parse version identifier, and convert them
4//! between multiple standards such as
5//! [semver.org's Semantic Versioning](https://semver.org/),
6//! [Python's PEP-440](https://www.python.org/dev/peps/pep-0440/), etc.
7//!
8//! # Why not stick with semantic versioning?
9//!
10//! Semantic versioning has the benefit of being a standard, and is
11//! (sort-of) used by some ecosystems, but has many short-comings. For example,
12//! it does not support post-releases. It also has some (minor?)
13//! incompatibilities with some (most?) standards already in use before its
14//! inception, such as Git (output of `git describe`), Debian, Fedora, Python,
15//! Conda, etc. A lot of important features were disregarded in the creation of
16//! semver (such as post-releases or third-party packaging), and some
17//! incompatible choices were made (having the dash automatically indicate a
18//! pre-release).
19//!
20//! Fortunately most of the other versioning schemes are still used when
21//! appropriate.
22//!
23//! This library intends to support most of the version numbers actually in the
24//! wild and to provide conversion utilities where possible.
25//!
26//! # Functionality
27//!
28//! This library is based around Debian's versioning scheme, which is both very
29//! expressive (used by the Debian and Ubuntu communities to version millions
30//! of packages of all kinds of software), very compatible (accepts any version
31//! string under the sun), and proved (Debian has been around for a while).
32//!
33//! It can also represent and "import" version numbers from foreign schemes,
34//! such semver, PEP-440, etc.
35//!
36//! If you want to parse random version numbers that you can't assume follow
37//! semver, this library is probably what you want.
38
39mod cmp;
40pub mod debian;
41pub mod python;
42pub mod semver;
43mod utils;
44
45use std::cmp::{Ordering, PartialOrd};
46use std::convert::TryFrom;
47use std::fmt;
48use std::ops::Deref;
49
50use cmp::{CHAR_ORDER, compare_versions};
51use utils::NumChecker;
52
53/// A version number.
54#[derive(Clone, Debug, Hash)]
55pub struct Version(String);
56
57impl Deref for Version {
58    type Target = str;
59
60    fn deref(&self) -> &str {
61        &self.0
62    }
63}
64
65impl fmt::Display for Version {
66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67        write!(f, "{}", self.0)
68    }
69}
70
71impl PartialEq<Version> for Version {
72    fn eq(&self, other: &Version) -> bool {
73        self.cmp(other) == Ordering::Equal
74    }
75}
76
77impl Eq for Version {}
78
79impl PartialOrd<Version> for Version {
80    fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
81        Some(self.cmp(other))
82    }
83}
84
85impl Ord for Version {
86    fn cmp(&self, other: &Version) -> Ordering {
87        compare_versions(&self.0, &other.0)
88    }
89}
90
91impl Version {
92    pub fn epoch() -> u32 {
93        unimplemented!() // TODO: Read epoch (default to 0)
94    }
95}
96
97/// Error for the version parser.
98pub enum InvalidVersion {
99    /// The version contains invalid characters.
100    InvalidCharacter,
101    /// The version number contains numeric fields with a leading zero.
102    LeadingZero,
103    /// Empty field (for example, two consecutive dots).
104    EmptyField,
105    #[doc(hidden)]
106    __Nonexhaustive,
107}
108
109impl TryFrom<String> for Version {
110    type Error = InvalidVersion;
111
112    fn try_from(string: String) -> Result<Version, InvalidVersion> {
113        for c in string.bytes() {
114            // Check characters are allowed
115            if CHAR_ORDER[usize::from(c)] == 255 {
116                return Err(InvalidVersion::InvalidCharacter);
117            }
118        }
119        Ok(Version(string))
120    }
121}
122
123/// A simple "old-school" version number (only numbers and dots).
124///
125/// This is of special interest because it should be compatible and unambiguous
126/// in all versioning schemes (expect for semver's requirement that there be
127/// exactly 3 fields).
128#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
129pub struct SimpleVersion(Version);
130
131impl AsRef<Version> for SimpleVersion {
132    fn as_ref(&self) -> &Version {
133        &self.0
134    }
135}
136
137impl TryFrom<String> for SimpleVersion {
138    type Error = InvalidVersion;
139
140    fn try_from(string: String) -> Result<SimpleVersion, InvalidVersion> {
141        let mut num_check = NumChecker::new();
142        // Check characters are allowed
143        for c in string.bytes() {
144            if c == b'.' {
145                if num_check == NumChecker::Start {
146                    return Err(InvalidVersion::EmptyField);
147                }
148            } else if c < b'0' || b'9' < c {
149                return Err(InvalidVersion::InvalidCharacter);
150            }
151            if !num_check.check(c) {
152                return Err(InvalidVersion::LeadingZero);
153            }
154        }
155        if num_check == NumChecker::Start {
156            return Err(InvalidVersion::EmptyField);
157        }
158        Ok(SimpleVersion(Version(string)))
159    }
160}