1use std::{
2 fmt::{self, Display, Formatter},
3 str::FromStr,
4};
5
6#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
8pub struct Version(u16, u16, u16, bool);
9
10impl Version {
11 pub fn new(major: u16, minor: u16, patch: u16) -> Self {
12 Self(major, minor, patch, true)
13 }
14
15 pub fn to_cfg(self, prefix: Option<&str>) -> String {
23 if self.3 {
24 if let Some(p) = prefix {
25 format!("feature = \"{}_{}\"", p, self.to_feature())
26 } else {
27 format!("feature = \"{}\"", self.to_feature())
28 }
29 } else if let Some(p) = prefix {
30 format!("not(feature = \"{}_{}\")", p, self.to_feature())
31 } else {
32 format!("not(feature = \"{}\")", self.to_feature())
33 }
34 }
35
36 pub fn as_opposite(&mut self) {
37 self.3 = !self.3;
38 }
39
40 pub fn to_feature(self) -> String {
41 match self {
42 Self(major, 0, 0, _) => format!("v{major}"),
43 Self(major, minor, 0, _) => format!("v{major}_{minor}"),
44 Self(major, minor, patch, _) => format!("v{major}_{minor}_{patch}"),
45 }
46 }
47
48 pub fn if_stricter_than(
51 inner_version: Option<Self>,
52 outer_version: Option<Self>,
53 ) -> Option<Self> {
54 match (inner_version, outer_version) {
55 (Some(inner_version), Some(outer_version)) if inner_version <= outer_version => None,
56 (inner_version, _) => inner_version,
57 }
58 }
59}
60
61impl FromStr for Version {
62 type Err = String;
63
64 fn from_str(s: &str) -> Result<Self, String> {
67 if s.contains('.') {
68 let mut parts = s
69 .splitn(4, '.')
70 .map(str::parse)
71 .take_while(Result::is_ok)
72 .map(Result::unwrap);
73 Ok(Self::new(
74 parts.next().unwrap_or(0),
75 parts.next().unwrap_or(0),
76 parts.next().unwrap_or(0),
77 ))
78 } else {
79 let val = s.parse::<u16>();
80 Ok(Self::new(val.unwrap_or(0), 0, 0))
81 }
82 }
83}
84
85impl Display for Version {
86 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
87 match *self {
88 Self(major, 0, 0, _) => write!(f, "{major}"),
89 Self(major, minor, 0, _) => write!(f, "{major}.{minor}"),
90 Self(major, minor, patch, _) => write!(f, "{major}.{minor}.{patch}"),
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use std::str::FromStr;
98
99 use super::Version;
100
101 #[test]
102 fn from_str_works() {
103 assert_eq!(FromStr::from_str("1"), Ok(Version::new(1, 0, 0)));
104 assert_eq!(FromStr::from_str("2.1"), Ok(Version::new(2, 1, 0)));
105 assert_eq!(FromStr::from_str("3.2.1"), Ok(Version::new(3, 2, 1)));
106 assert_eq!(FromStr::from_str("3.ff.1"), Ok(Version::new(3, 0, 0)));
107 }
108
109 #[test]
110 fn parse_works() {
111 assert_eq!("1".parse(), Ok(Version::new(1, 0, 0)));
112 }
113
114 #[test]
115 fn ord() {
116 assert!(Version::new(0, 0, 0) < Version::new(1, 2, 3));
117 assert!(Version::new(1, 0, 0) < Version::new(1, 2, 3));
118 assert!(Version::new(1, 2, 0) < Version::new(1, 2, 3));
119 assert!(Version::new(1, 2, 3) == Version::new(1, 2, 3));
120 assert!(Version::new(1, 0, 0) < Version::new(2, 0, 0));
121 assert!(Version::new(3, 0, 0) == Version::new(3, 0, 0));
122 }
123}