1 | use std::fmt; |
2 | use std::str::from_utf8; |
3 | |
4 | use recognize::*; |
5 | |
6 | use common::{self, numeric_identifier}; |
7 | |
8 | #[derive (Clone, Debug, PartialEq, Eq)] |
9 | pub struct Version { |
10 | pub major: u64, |
11 | pub minor: u64, |
12 | pub patch: u64, |
13 | pub pre: Vec<Identifier>, |
14 | pub build: Vec<Identifier>, |
15 | } |
16 | |
17 | #[derive (Clone, Debug, PartialEq, Eq)] |
18 | pub enum Identifier { |
19 | /// An identifier that's solely numbers. |
20 | Numeric(u64), |
21 | /// An identifier with letters and numbers. |
22 | AlphaNumeric(String), |
23 | } |
24 | |
25 | pub fn parse(version: &str) -> Result<Version, String> { |
26 | let s = version.trim().as_bytes(); |
27 | let mut i = 0; |
28 | let major = if let Some((major, len)) = numeric_identifier(&s[i..]) { |
29 | i += len; |
30 | major |
31 | } else { |
32 | return Err("Error parsing major identifier" .to_string()); |
33 | }; |
34 | if let Some(len) = b'.' .p(&s[i..]) { |
35 | i += len; |
36 | } else { |
37 | return Err("Expected dot" .to_string()); |
38 | } |
39 | let minor = if let Some((minor, len)) = numeric_identifier(&s[i..]) { |
40 | i += len; |
41 | minor |
42 | } else { |
43 | return Err("Error parsing minor identifier" .to_string()); |
44 | }; |
45 | if let Some(len) = b'.' .p(&s[i..]) { |
46 | i += len; |
47 | } else { |
48 | return Err("Expected dot" .to_string()); |
49 | } |
50 | let patch = if let Some((patch, len)) = numeric_identifier(&s[i..]) { |
51 | i += len; |
52 | patch |
53 | } else { |
54 | return Err("Error parsing patch identifier" .to_string()); |
55 | }; |
56 | let (pre, pre_len) = common::parse_optional_meta(&s[i..], b'-' )?; |
57 | i += pre_len; |
58 | let (build, build_len) = common::parse_optional_meta(&s[i..], b'+' )?; |
59 | i += build_len; |
60 | if i != s.len() { |
61 | return Err("Extra junk after valid version: " .to_string() + |
62 | from_utf8(&s[i..]).unwrap()); |
63 | } |
64 | Ok(Version { |
65 | major: major, |
66 | minor: minor, |
67 | patch: patch, |
68 | pre: pre, |
69 | build: build, |
70 | }) |
71 | } |
72 | |
73 | impl fmt::Display for Version { |
74 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
75 | try!(write!(f, " {}. {}. {}" , self.major, self.minor, self.patch)); |
76 | if !self.pre.is_empty() { |
77 | let strs: Vec<_> = |
78 | self.pre.iter().map(ToString::to_string).collect(); |
79 | try!(write!(f, "- {}" , strs.join("." ))); |
80 | } |
81 | if !self.build.is_empty() { |
82 | let strs: Vec<_> = |
83 | self.build.iter().map(ToString::to_string).collect(); |
84 | try!(write!(f, "+ {}" , strs.join("." ))); |
85 | } |
86 | Ok(()) |
87 | } |
88 | } |
89 | |
90 | impl fmt::Display for Identifier { |
91 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
92 | match *self { |
93 | Identifier::Numeric(ref id: &u64) => id.fmt(f), |
94 | Identifier::AlphaNumeric(ref id: &String) => id.fmt(f), |
95 | } |
96 | } |
97 | } |
98 | |
99 | #[cfg (test)] |
100 | mod tests { |
101 | use version; |
102 | use super::*; |
103 | |
104 | #[test ] |
105 | fn parse_empty() { |
106 | let version = "" ; |
107 | |
108 | let parsed = version::parse(version); |
109 | |
110 | assert!(parsed.is_err(), "empty string incorrectly considered a valid parse" ); |
111 | } |
112 | |
113 | #[test ] |
114 | fn parse_blank() { |
115 | let version = " " ; |
116 | |
117 | let parsed = version::parse(version); |
118 | |
119 | assert!(parsed.is_err(), "blank string incorrectly considered a valid parse" ); |
120 | } |
121 | |
122 | #[test ] |
123 | fn parse_no_minor_patch() { |
124 | let version = "1" ; |
125 | |
126 | let parsed = version::parse(version); |
127 | |
128 | assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse" , version)); |
129 | } |
130 | |
131 | #[test ] |
132 | fn parse_no_patch() { |
133 | let version = "1.2" ; |
134 | |
135 | let parsed = version::parse(version); |
136 | |
137 | assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse" , version)); |
138 | } |
139 | |
140 | #[test ] |
141 | fn parse_empty_pre() { |
142 | let version = "1.2.3-" ; |
143 | |
144 | let parsed = version::parse(version); |
145 | |
146 | assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse" , version)); |
147 | } |
148 | |
149 | #[test ] |
150 | fn parse_letters() { |
151 | let version = "a.b.c" ; |
152 | |
153 | let parsed = version::parse(version); |
154 | |
155 | assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse" , version)); |
156 | } |
157 | |
158 | #[test ] |
159 | fn parse_with_letters() { |
160 | let version = "1.2.3 a.b.c" ; |
161 | |
162 | let parsed = version::parse(version); |
163 | |
164 | assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse" , version)); |
165 | } |
166 | |
167 | #[test ] |
168 | fn parse_basic_version() { |
169 | let version = "1.2.3" ; |
170 | |
171 | let parsed = version::parse(version).unwrap(); |
172 | |
173 | assert_eq!(1, parsed.major); |
174 | assert_eq!(2, parsed.minor); |
175 | assert_eq!(3, parsed.patch); |
176 | } |
177 | |
178 | #[test ] |
179 | fn parse_trims_input() { |
180 | let version = " 1.2.3 " ; |
181 | |
182 | let parsed = version::parse(version).unwrap(); |
183 | |
184 | assert_eq!(1, parsed.major); |
185 | assert_eq!(2, parsed.minor); |
186 | assert_eq!(3, parsed.patch); |
187 | } |
188 | |
189 | #[test ] |
190 | fn parse_no_major_leading_zeroes() { |
191 | let version = "01.0.0" ; |
192 | |
193 | let parsed = version::parse(version); |
194 | |
195 | assert!(parsed.is_err(), "01 incorrectly considered a valid major version" ); |
196 | } |
197 | |
198 | #[test ] |
199 | fn parse_no_minor_leading_zeroes() { |
200 | let version = "0.01.0" ; |
201 | |
202 | let parsed = version::parse(version); |
203 | |
204 | assert!(parsed.is_err(), "01 incorrectly considered a valid minor version" ); |
205 | } |
206 | |
207 | #[test ] |
208 | fn parse_no_patch_leading_zeroes() { |
209 | let version = "0.0.01" ; |
210 | |
211 | let parsed = version::parse(version); |
212 | |
213 | assert!(parsed.is_err(), "01 incorrectly considered a valid patch version" ); |
214 | } |
215 | |
216 | #[test ] |
217 | fn parse_no_major_overflow() { |
218 | let version = "98765432109876543210.0.0" ; |
219 | |
220 | let parsed = version::parse(version); |
221 | |
222 | assert!(parsed.is_err(), "98765432109876543210 incorrectly considered a valid major version" ); |
223 | } |
224 | |
225 | #[test ] |
226 | fn parse_no_minor_overflow() { |
227 | let version = "0.98765432109876543210.0" ; |
228 | |
229 | let parsed = version::parse(version); |
230 | |
231 | assert!(parsed.is_err(), "98765432109876543210 incorrectly considered a valid minor version" ); |
232 | } |
233 | |
234 | #[test ] |
235 | fn parse_no_patch_overflow() { |
236 | let version = "0.0.98765432109876543210" ; |
237 | |
238 | let parsed = version::parse(version); |
239 | |
240 | assert!(parsed.is_err(), "98765432109876543210 incorrectly considered a valid patch version" ); |
241 | } |
242 | |
243 | #[test ] |
244 | fn parse_basic_prerelease() { |
245 | let version = "1.2.3-pre" ; |
246 | |
247 | let parsed = version::parse(version).unwrap(); |
248 | |
249 | let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre" ))]; |
250 | assert_eq!(expected_pre, parsed.pre); |
251 | } |
252 | |
253 | #[test ] |
254 | fn parse_prerelease_alphanumeric() { |
255 | let version = "1.2.3-alpha1" ; |
256 | |
257 | let parsed = version::parse(version).unwrap(); |
258 | |
259 | let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1" ))]; |
260 | assert_eq!(expected_pre, parsed.pre); |
261 | } |
262 | |
263 | #[test ] |
264 | fn parse_prerelease_zero() { |
265 | let version = "1.2.3-pre.0" ; |
266 | |
267 | let parsed = version::parse(version).unwrap(); |
268 | |
269 | let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre" )), |
270 | Identifier::Numeric(0)]; |
271 | assert_eq!(expected_pre, parsed.pre); |
272 | } |
273 | |
274 | #[test ] |
275 | fn parse_basic_build() { |
276 | let version = "1.2.3+build" ; |
277 | |
278 | let parsed = version::parse(version).unwrap(); |
279 | |
280 | let expected_build = vec![Identifier::AlphaNumeric(String::from("build" ))]; |
281 | assert_eq!(expected_build, parsed.build); |
282 | } |
283 | |
284 | #[test ] |
285 | fn parse_build_alphanumeric() { |
286 | let version = "1.2.3+build5" ; |
287 | |
288 | let parsed = version::parse(version).unwrap(); |
289 | |
290 | let expected_build = vec![Identifier::AlphaNumeric(String::from("build5" ))]; |
291 | assert_eq!(expected_build, parsed.build); |
292 | } |
293 | |
294 | #[test ] |
295 | fn parse_pre_and_build() { |
296 | let version = "1.2.3-alpha1+build5" ; |
297 | |
298 | let parsed = version::parse(version).unwrap(); |
299 | |
300 | let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1" ))]; |
301 | assert_eq!(expected_pre, parsed.pre); |
302 | |
303 | let expected_build = vec![Identifier::AlphaNumeric(String::from("build5" ))]; |
304 | assert_eq!(expected_build, parsed.build); |
305 | } |
306 | |
307 | #[test ] |
308 | fn parse_complex_metadata_01() { |
309 | let version = "1.2.3-1.alpha1.9+build5.7.3aedf " ; |
310 | |
311 | let parsed = version::parse(version).unwrap(); |
312 | |
313 | let expected_pre = vec![Identifier::Numeric(1), |
314 | Identifier::AlphaNumeric(String::from("alpha1" )), |
315 | Identifier::Numeric(9)]; |
316 | assert_eq!(expected_pre, parsed.pre); |
317 | |
318 | let expected_build = vec![Identifier::AlphaNumeric(String::from("build5" )), |
319 | Identifier::Numeric(7), |
320 | Identifier::AlphaNumeric(String::from("3aedf" ))]; |
321 | assert_eq!(expected_build, parsed.build); |
322 | } |
323 | |
324 | #[test ] |
325 | fn parse_complex_metadata_02() { |
326 | let version = "0.4.0-beta.1+0851523" ; |
327 | |
328 | let parsed = version::parse(version).unwrap(); |
329 | |
330 | let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta" )), |
331 | Identifier::Numeric(1)]; |
332 | assert_eq!(expected_pre, parsed.pre); |
333 | |
334 | let expected_build = vec![Identifier::AlphaNumeric(String::from("0851523" ))]; |
335 | assert_eq!(expected_build, parsed.build); |
336 | } |
337 | |
338 | #[test ] |
339 | fn parse_metadata_overflow() { |
340 | let version = "0.4.0-beta.1+98765432109876543210" ; |
341 | |
342 | let parsed = version::parse(version).unwrap(); |
343 | |
344 | let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta" )), |
345 | Identifier::Numeric(1)]; |
346 | assert_eq!(expected_pre, parsed.pre); |
347 | |
348 | let expected_build = vec![Identifier::AlphaNumeric(String::from("98765432109876543210" ))]; |
349 | assert_eq!(expected_build, parsed.build); |
350 | } |
351 | |
352 | #[test ] |
353 | fn parse_regression_01() { |
354 | let version = "0.0.0-WIP" ; |
355 | |
356 | let parsed = version::parse(version).unwrap(); |
357 | |
358 | assert_eq!(0, parsed.major); |
359 | assert_eq!(0, parsed.minor); |
360 | assert_eq!(0, parsed.patch); |
361 | |
362 | let expected_pre = vec![Identifier::AlphaNumeric(String::from("WIP" ))]; |
363 | assert_eq!(expected_pre, parsed.pre); |
364 | } |
365 | } |
366 | |