r_description/
version.rs

1//! R Version strings
2use std::cmp::Ordering;
3
4#[derive(Debug, PartialEq, Eq, std::hash::Hash, Clone)]
5/// Represents a version string like "1.2.3" or "1.2.3-alpha"
6pub struct Version {
7    /// Version components like [1, 2, 3]
8    pub components: Vec<u32>,
9    /// Pre-release version like "alpha", "beta", etc.
10    pub pre_release: Option<String>, // Pre-release version like "alpha", "beta", etc.
11}
12
13impl std::fmt::Display for Version {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        // Format the version string as "major.minor.patch" or "major.minor.patch-pre_release"
16        f.write_str(
17            &self
18                .components
19                .iter()
20                .map(|c| c.to_string())
21                .collect::<Vec<_>>()
22                .join("."),
23        )?;
24        if let Some(pre_release) = &self.pre_release {
25            f.write_str("-")?;
26            f.write_str(pre_release)?;
27        }
28        Ok(())
29    }
30}
31
32impl Version {
33    /// Create a new version
34    pub fn new(major: u32, minor: u32, patch: Option<u32>, pre_release: Option<&str>) -> Self {
35        Self {
36            components: if let Some(patch) = patch {
37                vec![major, minor, patch]
38            } else {
39                vec![major, minor]
40            },
41            pre_release: pre_release.map(|s| s.to_string()),
42        }
43    }
44}
45
46impl std::str::FromStr for Version {
47    type Err = String;
48
49    fn from_str(s: &str) -> Result<Self, Self::Err> {
50        // Split the version string by '.' and '-' to get major, minor, patch, and pre-release
51        let mut parts = s.splitn(2, '-');
52        let version = parts
53            .next()
54            .ok_or(format!("Invalid version string: {s}"))?;
55        let pre_release = parts.next().map(|s| s.to_string());
56
57        let components = version
58            .split('.')
59            .map(|part| {
60                part.parse()
61                    .map_err(|_| format!("Invalid version component: {s}"))
62            })
63            .collect::<Result<Vec<_>, _>>()?;
64
65        Ok(Self {
66            components,
67            pre_release,
68        })
69    }
70}
71
72impl Ord for Version {
73    fn cmp(&self, other: &Self) -> Ordering {
74        // Compare components in order, and then compare pre-release tags
75        for (a, b) in self.components.iter().zip(other.components.iter()) {
76            match a.cmp(b) {
77                Ordering::Equal => continue,
78                ordering => return ordering,
79            }
80        }
81        if self.components.len() < other.components.len() {
82            Ordering::Less
83        } else if self.components.len() > other.components.len() {
84            Ordering::Greater
85        } else {
86            self.compare_pre_release(other)
87        }
88    }
89}
90
91impl PartialOrd for Version {
92    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
93        Some(self.cmp(other))
94    }
95}
96
97impl Version {
98    fn compare_pre_release(&self, other: &Self) -> Ordering {
99        match (&self.pre_release, &other.pre_release) {
100            (None, None) => Ordering::Equal,
101            (None, Some(_)) => Ordering::Greater,
102            (Some(_), None) => Ordering::Less,
103            (Some(a), Some(b)) => a.cmp(b),
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::Version;
111    use std::str::FromStr;
112
113    #[test]
114    fn test_version_from_str() {
115        use std::str::FromStr;
116
117        let version = Version::from_str("1.2.3").unwrap();
118        assert_eq!(version, Version::new(1, 2, Some(3), None));
119
120        let version = Version::from_str("1.2.3-alpha").unwrap();
121        assert_eq!(version, Version::new(1, 2, Some(3), Some("alpha")));
122
123        let version = Version::from_str("1.2.3-beta").unwrap();
124        assert_eq!(version, Version::new(1, 2, Some(3), Some("beta")));
125    }
126
127    #[test]
128    fn test_version_cmp() {
129        use std::cmp::Ordering;
130
131        let v1 = Version::from_str("1.2.3").unwrap();
132        let v2 = Version::from_str("1.2.3").unwrap();
133        assert_eq!(v1.cmp(&v2), Ordering::Equal);
134
135        let v1 = Version::from_str("1.2.3").unwrap();
136        let v2 = Version::from_str("1.2.4").unwrap();
137        assert_eq!(v1.cmp(&v2), Ordering::Less);
138
139        let v1 = Version::from_str("1.2.3").unwrap();
140        let v2 = Version::from_str("1.2.3-alpha").unwrap();
141        assert_eq!(v1.cmp(&v2), Ordering::Greater);
142
143        let v1 = Version::from_str("1.2.3-alpha").unwrap();
144        let v2 = Version::from_str("1.2.3-beta").unwrap();
145        assert_eq!(v1.cmp(&v2), Ordering::Less);
146    }
147
148    #[test]
149    fn test_version_invalid() {
150        assert!(Version::from_str("a").is_err());
151        assert!(Version::from_str("a1-b").is_err());
152    }
153}