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
parse(version: &str) -> Result<Version, String>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 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result74 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 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 match *self {
93 Identifier::Numeric(ref id) => id.fmt(f),
94 Identifier::AlphaNumeric(ref id) => id.fmt(f),
95 }
96 }
97 }
98
99 #[cfg(test)]
100 mod tests {
101 use version;
102 use super::*;
103
104 #[test]
parse_empty()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]
parse_blank()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]
parse_no_minor_patch()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]
parse_no_patch()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]
parse_empty_pre()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]
parse_letters()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]
parse_with_letters()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]
parse_basic_version()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]
parse_trims_input()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]
parse_no_major_leading_zeroes()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]
parse_no_minor_leading_zeroes()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]
parse_no_patch_leading_zeroes()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]
parse_no_major_overflow()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]
parse_no_minor_overflow()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]
parse_no_patch_overflow()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]
parse_basic_prerelease()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]
parse_prerelease_alphanumeric()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]
parse_prerelease_zero()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]
parse_basic_build()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]
parse_build_alphanumeric()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]
parse_pre_and_build()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]
parse_complex_metadata_01()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]
parse_complex_metadata_02()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]
parse_metadata_overflow()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]
parse_regression_01()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