1use std::{
2 cmp,
3 fmt::{self, Display, Formatter},
4 str::FromStr,
5};
6
7use crate::error::ParseVersionError;
8
9#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
23pub struct FactorioVersion {
24 pub major: u64,
26
27 pub minor: u64,
29
30 pub patch: Option<u64>,
32}
33
34impl FactorioVersion {
35 pub fn new(major: u64, minor: u64) -> Self {
40 Self {
41 major,
42 minor,
43 patch: None,
44 }
45 }
46
47 pub fn with_patch(major: u64, minor: u64, patch: u64) -> Self {
48 Self {
49 major,
50 minor,
51 patch: Some(patch),
52 }
53 }
54
55 pub fn parse(s: &str) -> Result<Self, ParseVersionError> {
73 s.parse()
74 }
75
76 pub(crate) fn create(major: u64, minor: u64, patch: Option<u64>) -> Self {
82 Self {
83 major,
84 minor,
85 patch,
86 }
87 }
88}
89
90impl Default for FactorioVersion {
91 fn default() -> Self {
92 Self {
93 major: 0,
94 minor: 12,
95 patch: None,
96 }
97 }
98}
99
100impl Ord for FactorioVersion {
101 fn cmp(&self, other: &Self) -> cmp::Ordering {
102 use cmp::Ordering::*;
103 match self.major.cmp(&other.major) {
104 Equal => match self.minor.cmp(&other.minor) {
105 Equal => match (self.patch, other.patch) {
106 (Some(self_patch), Some(other_patch)) => self_patch.cmp(&other_patch),
107 (Some(_), None) => Less,
108 (None, Some(_)) => Greater,
109 (None, None) => Equal,
110 },
111 ordering => ordering,
112 },
113 ordering => ordering,
114 }
115 }
116}
117
118impl PartialOrd for FactorioVersion {
119 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
120 Some(self.cmp(other))
121 }
122}
123
124impl FromStr for FactorioVersion {
125 type Err = ParseVersionError;
126
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 let parts = s.trim().split('.').map(|p| p.trim()).collect::<Vec<_>>();
129
130 if parts.len() > 3 {
131 return Err(ParseVersionError::Size(2, parts.len()));
132 }
133
134 let major = parts[0].parse().map_err(ParseVersionError::Major)?;
135 let minor = parts[1].parse().map_err(ParseVersionError::Minor)?;
136
137 let patch: Option<u64> = if parts.len() == 3 {
138 Some(parts[2].parse().map_err(ParseVersionError::Patch)?)
139 } else {
140 None
141 };
142
143 Ok(FactorioVersion::create(major, minor, patch))
144 }
145}
146
147impl Display for FactorioVersion {
148 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
149 write!(f, "{}.{}", self.major, self.minor)?;
150
151 if let Some(patch) = self.patch {
152 write!(f, ".{}", patch)?;
153 }
154
155 Ok(())
156 }
157}
158
159#[cfg(test)]
160mod test_super {
161 use std::cmp;
162
163 use super::*;
164
165 #[test]
166 fn test_parse() {
167 assert_eq!(
168 FactorioVersion::parse("1.80").unwrap(),
169 FactorioVersion::new(1, 80)
170 );
171 }
172
173 #[test]
174 fn test_parse_patch() {
175 assert_eq!(
176 FactorioVersion::parse("1.80.66").unwrap(),
177 FactorioVersion::with_patch(1, 80, 66)
178 );
179 }
180
181 #[test]
182 fn test_display() {
183 assert_eq!(format!("{}", FactorioVersion::new(1, 2)), "1.2");
184 }
185
186 #[test]
187 fn test_display_patch() {
188 assert_eq!(format!("{}", FactorioVersion::with_patch(1, 2, 3)), "1.2.3");
189 }
190
191 #[test]
192 fn test_ordering() {
193 let mut major_differs = vec![FactorioVersion::new(5, 1), FactorioVersion::new(1, 2)];
194 major_differs.sort();
195 assert_eq!(
196 major_differs,
197 vec![FactorioVersion::new(1, 2), FactorioVersion::new(5, 1)]
198 );
199 let mut minor_differs = vec![FactorioVersion::new(1, 5), FactorioVersion::new(1, 2)];
200 minor_differs.sort();
201 assert_eq!(
202 minor_differs,
203 vec![FactorioVersion::new(1, 2), FactorioVersion::new(1, 5)]
204 );
205 }
206
207 #[test]
215 fn test_nopatch_gt_haspatch() {
216 let no_patch = FactorioVersion::new(1, 2);
217 let has_patch = FactorioVersion::with_patch(1, 2, 0);
218
219 assert_eq!(no_patch.cmp(&has_patch), cmp::Ordering::Greater)
220 }
221}