Skip to content

Commit 1bfa9b3

Browse files
committed
Start range parsing
This works for everything except multiples.
1 parent 2fda5ee commit 1bfa9b3

File tree

4 files changed

+891
-325
lines changed

4 files changed

+891
-325
lines changed

src/common.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use regex::Regex;
2+
use version::Identifier;
3+
4+
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
5+
// a result or anything
6+
pub fn parse_meta(pre: &str) -> Vec<Identifier> {
7+
// Originally, I wanted to implement this method via calling parse, but parse is tolerant of
8+
// leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
9+
// numeric. So the strategy is to check with a regex first, and then call parse once we've
10+
// determined that it's a number without a leading zero.
11+
let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
12+
13+
pre.split(".")
14+
.map(|part| {
15+
// another wrinkle: we made sure that any number starts with a non-zero. But there's a
16+
// problem: an actual zero is a number, yet gets left out by this heuristic. So let's
17+
// also check for the single, lone zero.
18+
if regex.is_match(part) || part == "0" {
19+
// we can unwrap here because we know it is only digits due to the regex
20+
Identifier::Numeric(part.parse().unwrap())
21+
} else {
22+
Identifier::AlphaNumeric(part.to_string())
23+
}
24+
}).collect()
25+
}
26+

src/lib.rs

Lines changed: 4 additions & 325 deletions
Original file line numberDiff line numberDiff line change
@@ -3,329 +3,8 @@ extern crate regex;
33
#[macro_use]
44
extern crate lazy_static;
55

6-
use regex::Regex;
6+
pub mod version;
7+
pub mod range;
78

8-
lazy_static! {
9-
static ref REGEX: Regex = {
10-
// a numeric identifier is either zero or multiple numbers without a leading zero
11-
let numeric_identifier = r"0|(:?[1-9][0-9]*)";
12-
13-
let major = numeric_identifier;
14-
let minor = numeric_identifier;
15-
let patch = numeric_identifier;
16-
17-
let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
18-
19-
// This regex does not fully parse prereleases, just extracts the whole prerelease string.
20-
// parse_version() will parse this further.
21-
let pre = letters_numbers_dash_dot;
22-
23-
// This regex does not fully parse builds, just extracts the whole build string.
24-
// parse_version() will parse this further.
25-
let build = letters_numbers_dash_dot;
26-
27-
let regex = format!(r"^(?x) # heck yes x mode
28-
(?P<major>{}) # major version
29-
\. # dot
30-
(?P<minor>{}) # minor version
31-
\. # dot
32-
(?P<patch>{}) # patch version
33-
(:?-(?P<pre>{}))? # optional prerelease version
34-
(:?\+(?P<build>{}))? # optional build metadata
35-
$",
36-
major,
37-
minor,
38-
patch,
39-
pre,
40-
build);
41-
println!("{:?}", regex);
42-
let regex = Regex::new(&regex);
43-
44-
// this unwrap is okay because everything above here is const, so this will never fail.
45-
regex.unwrap()
46-
};
47-
}
48-
49-
pub struct Version {
50-
pub major: u64,
51-
pub minor: u64,
52-
pub patch: u64,
53-
pub pre: Option<Vec<Identifier>>,
54-
pub build: Option<Vec<Identifier>>,
55-
}
56-
57-
#[derive(Debug,PartialEq)]
58-
pub enum Identifier {
59-
/// An identifier that's solely numbers.
60-
Numeric(u64),
61-
/// An identifier with letters and numbers.
62-
AlphaNumeric(String),
63-
}
64-
65-
pub fn parse_version(version: &str) -> Result<Version, String> {
66-
let captures = match REGEX.captures(version.trim()) {
67-
Some(captures) => captures,
68-
None => return Err(From::from("Version did not parse properly.")),
69-
};
70-
71-
let pre = captures.name("pre").map(parse_meta);
72-
73-
let build = captures.name("build").map(parse_meta);
74-
75-
Ok(Version {
76-
major: captures.name("major").unwrap().parse().unwrap(),
77-
minor: captures.name("minor").unwrap().parse().unwrap(),
78-
patch: captures.name("patch").unwrap().parse().unwrap(),
79-
pre: pre,
80-
build: build,
81-
})
82-
}
83-
84-
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
85-
// a result or anything
86-
fn parse_meta(pre: &str) -> Vec<Identifier> {
87-
// Originally, I wanted to implement this method via calling parse, but parse is tolerant of
88-
// leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
89-
// numeric. So the strategy is to check with a regex first, and then call parse once we've
90-
// determined that it's a number without a leading zero.
91-
let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
92-
93-
pre.split(".")
94-
.map(|part| {
95-
// another wrinkle: we made sure that any number starts with a non-zero. But there's a
96-
// problem: an actual zero is a number, yet gets left out by this heuristic. So let's
97-
// also check for the single, lone zero.
98-
if regex.is_match(part) || part == "0" {
99-
// we can unwrap here because we know it is only digits due to the regex
100-
Identifier::Numeric(part.parse().unwrap())
101-
} else {
102-
Identifier::AlphaNumeric(part.to_string())
103-
}
104-
}).collect()
105-
}
106-
107-
#[cfg(test)]
108-
mod tests {
109-
use super::*;
110-
111-
#[test]
112-
fn parse_empty() {
113-
let version = "";
114-
115-
let parsed = parse_version(version);
116-
117-
assert!(parsed.is_err(), "empty string incorrectly considered a valid parse");
118-
}
119-
120-
#[test]
121-
fn parse_blank() {
122-
let version = " ";
123-
124-
let parsed = parse_version(version);
125-
126-
assert!(parsed.is_err(), "blank string incorrectly considered a valid parse");
127-
}
128-
129-
#[test]
130-
fn parse_no_minor_patch() {
131-
let version = "1";
132-
133-
let parsed = parse_version(version);
134-
135-
assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
136-
}
137-
138-
#[test]
139-
fn parse_no_patch() {
140-
let version = "1.2";
141-
142-
let parsed = parse_version(version);
143-
144-
assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
145-
}
146-
147-
#[test]
148-
fn parse_empty_pre() {
149-
let version = "1.2.3-";
150-
151-
let parsed = parse_version(version);
152-
153-
assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
154-
}
155-
156-
#[test]
157-
fn parse_letters() {
158-
let version = "a.b.c";
159-
160-
let parsed = parse_version(version);
161-
162-
assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
163-
}
164-
165-
#[test]
166-
fn parse_version_with_letters() {
167-
let version = "1.2.3 a.b.c";
168-
169-
let parsed = parse_version(version);
170-
171-
assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
172-
}
173-
174-
#[test]
175-
fn parse_basic_version() {
176-
let version = "1.2.3";
177-
178-
let parsed = parse_version(version).unwrap();
179-
180-
assert_eq!(1, parsed.major);
181-
assert_eq!(2, parsed.minor);
182-
assert_eq!(3, parsed.patch);
183-
}
184-
185-
#[test]
186-
fn parse_trims_input() {
187-
let version = " 1.2.3 ";
188-
189-
let parsed = parse_version(version).unwrap();
190-
191-
assert_eq!(1, parsed.major);
192-
assert_eq!(2, parsed.minor);
193-
assert_eq!(3, parsed.patch);
194-
}
195-
196-
#[test]
197-
fn parse_version_no_major_leading_zeroes() {
198-
let version = "01.0.0";
199-
200-
let parsed = parse_version(version);
201-
202-
assert!(parsed.is_err(), "01 incorrectly considered a valid major version");
203-
}
204-
205-
#[test]
206-
fn parse_version_no_minor_leading_zeroes() {
207-
let version = "0.01.0";
208-
209-
let parsed = parse_version(version);
210-
211-
assert!(parsed.is_err(), "01 incorrectly considered a valid minor version");
212-
}
213-
214-
#[test]
215-
fn parse_version_no_patch_leading_zeroes() {
216-
let version = "0.0.01";
217-
218-
let parsed = parse_version(version);
219-
220-
assert!(parsed.is_err(), "01 incorrectly considered a valid patch version");
221-
}
222-
223-
#[test]
224-
fn parse_version_basic_prerelease() {
225-
let version = "1.2.3-pre";
226-
227-
let parsed = parse_version(version).unwrap();
228-
229-
let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre"))]);
230-
assert_eq!(expected_pre, parsed.pre);
231-
}
232-
233-
#[test]
234-
fn parse_version_prerelease_alphanumeric() {
235-
let version = "1.2.3-alpha1";
236-
237-
let parsed = parse_version(version).unwrap();
238-
239-
let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
240-
assert_eq!(expected_pre, parsed.pre);
241-
}
242-
243-
#[test]
244-
fn parse_version_prerelease_zero() {
245-
let version = "1.2.3-pre.0";
246-
247-
let parsed = parse_version(version).unwrap();
248-
249-
let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre")),
250-
Identifier::Numeric(0)]);
251-
assert_eq!(expected_pre, parsed.pre);
252-
}
253-
254-
#[test]
255-
fn parse_version_basic_build() {
256-
let version = "1.2.3+build";
257-
258-
let parsed = parse_version(version).unwrap();
259-
260-
let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build"))]);
261-
assert_eq!(expected_build, parsed.build);
262-
}
263-
264-
#[test]
265-
fn parse_version_build_alphanumeric() {
266-
let version = "1.2.3+build5";
267-
268-
let parsed = parse_version(version).unwrap();
269-
270-
let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5"))]);
271-
assert_eq!(expected_build, parsed.build);
272-
}
273-
274-
#[test]
275-
fn parse_version_pre_and_build() {
276-
let version = "1.2.3-alpha1+build5";
277-
278-
let parsed = parse_version(version).unwrap();
279-
280-
let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
281-
assert_eq!(expected_pre, parsed.pre);
282-
283-
let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5"))]);
284-
assert_eq!(expected_build, parsed.build);
285-
}
286-
287-
#[test]
288-
fn parse_version_complex_metadata_01() {
289-
let version = "1.2.3-1.alpha1.9+build5.7.3aedf ";
290-
291-
let parsed = parse_version(version).unwrap();
292-
293-
let expected_pre = Some(vec![Identifier::Numeric(1),
294-
Identifier::AlphaNumeric(String::from("alpha1")),
295-
Identifier::Numeric(9)]);
296-
assert_eq!(expected_pre, parsed.pre);
297-
298-
let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5")),
299-
Identifier::Numeric(7),
300-
Identifier::AlphaNumeric(String::from("3aedf"))]);
301-
assert_eq!(expected_build, parsed.build);
302-
}
303-
304-
#[test]
305-
fn parse_version_complex_metadata_02() {
306-
let version = "0.4.0-beta.1+0851523";
307-
308-
let parsed = parse_version(version).unwrap();
309-
310-
let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("beta")),
311-
Identifier::Numeric(1)]);
312-
assert_eq!(expected_pre, parsed.pre);
313-
314-
let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("0851523"))]);
315-
assert_eq!(expected_build, parsed.build);
316-
}
317-
318-
#[test]
319-
fn parse_regression_01() {
320-
let version = "0.0.0-WIP";
321-
322-
let parsed = parse_version(version).unwrap();
323-
324-
assert_eq!(0, parsed.major);
325-
assert_eq!(0, parsed.minor);
326-
assert_eq!(0, parsed.patch);
327-
328-
let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("WIP"))]);
329-
assert_eq!(expected_pre, parsed.pre);
330-
}
331-
}
9+
// for private stuff the two share
10+
mod common;

0 commit comments

Comments
 (0)