1#![cfg_attr(test, feature(plugin))]
2#![cfg_attr(test, plugin(clippy))]
3
4use std::cmp::Ordering;
5use std::fmt::{self, Write};
6use std::num::ParseIntError;
7use std::str::FromStr;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
11pub struct Number {
12 value: u64,
13 leading_zeroes: u8,
14}
15impl Number {
16 pub fn strip_zeroes(self) -> Number {
18 Number { value: self.value, leading_zeroes: 0 }
19 }
20
21 pub fn into_value(self) -> u64 {
23 self.value
24 }
25}
26impl PartialOrd for Number {
27 fn partial_cmp(&self, rhs: &Number) -> Option<Ordering> {
28 Some(self.cmp(rhs))
29 }
30}
31impl Ord for Number {
32 fn cmp(&self, rhs: &Number) -> Ordering {
33 match self.value.cmp(&rhs.value) {
34 Ordering::Equal => rhs.leading_zeroes.cmp(&self.leading_zeroes),
35 ord => ord,
36 }
37 }
38}
39impl From<u64> for Number {
40 fn from(value: u64) -> Number {
41 Number { value, leading_zeroes: 0 }
42 }
43}
44impl FromStr for Number {
45 type Err = ParseIntError;
46 fn from_str(s: &str) -> Result<Number, ParseIntError> {
47 let value = s.parse()?;
48 let leading_zeroes = s.bytes().position(|b| b != b'0').unwrap_or(0) as u8;
49 Ok(Number { value, leading_zeroes })
50 }
51}
52impl fmt::Display for Number {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 for _ in 0..self.leading_zeroes {
55 f.write_char('0')?;
56 }
57 fmt::Display::fmt(&self.value, f)
58 }
59}
60
61#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
65pub enum Part<'a> {
66 Num(Number),
68
69 Str(&'a str),
71}
72impl<'a> Part<'a> {
73 pub fn extract_from(s: &'a str) -> Option<(Part<'a>, &'a str)> {
75 let bs = s.as_bytes();
76
77 if let Some((&b, bs)) = bs.split_first() {
79 match b {
80 b'0'...b'9' => {
82 let split = bs.iter()
83 .position(|&b| b < b'0' || b'9' < b)
84 .map(|i| i + 1)
85 .unwrap_or(s.len());
86 let part = Part::Num(s[..split].parse().unwrap());
87 let rest = &s[split..];
88 Some((part, rest))
89 }
90 _ => {
92 let split = bs.iter()
93 .position(|&b| b'0' <= b && b <= b'9')
94 .map(|i| i + 1)
95 .unwrap_or(s.len());
96 let part = Part::Str(&s[..split]);
97 let rest = &s[split..];
98 Some((part, rest))
99 }
100 }
101 } else {
103 None
104 }
105 }
106
107 pub fn strip_zeroes(self) -> Part<'a> {
109 match self {
110 Part::Num(num) => Part::Num(num.strip_zeroes()),
111 Part::Str(s) => Part::Str(s),
112 }
113 }
114}
115
116#[derive(Clone, Debug)]
119pub struct Version<'a> {
120 inner: &'a str,
121}
122impl<'a> Version<'a> {
123 pub fn new(s: &'a str) -> Version<'a> {
125 Version { inner: s }
126 }
127}
128impl<'a> Iterator for Version<'a> {
129 type Item = Part<'a>;
130 fn next(&mut self) -> Option<Part<'a>> {
131 Part::extract_from(self.inner).map(|(part, rest)| {
132 self.inner = rest;
133 part
134 })
135 }
136}
137impl<'a> PartialEq for Version<'a> {
138 fn eq(&self, rhs: &Version<'a>) -> bool {
139 self.clone().eq(rhs.clone())
140 }
141}
142impl<'a> Eq for Version<'a> {}
143impl<'a> PartialOrd for Version<'a> {
144 fn partial_cmp(&self, rhs: &Version<'a>) -> Option<Ordering> {
145 self.clone().partial_cmp(rhs.clone())
146 }
147}
148impl<'a> Ord for Version<'a> {
149 fn cmp(&self, rhs: &Version<'a>) -> Ordering {
150 self.clone().cmp(rhs.clone())
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::{Number, Part, Version};
157
158 #[test]
159 fn lexical() {
160 assert!(Version::new("aaaaa") < Version::new("aaaaaa"));
161 }
162
163 #[test]
164 fn lexical_with_numbers() {
165 assert!(Version::new("abc123") > Version::new("abc0123"));
166 }
167
168 #[test]
169 fn nonlexical_float() {
170 assert!(Version::new("10.1") > Version::new("1.1"));
171 assert!(Version::new("1.10") > Version::new("1.1"));
172 }
173
174 #[test]
175 fn just_numbers() {
176 assert!(Version::new("2") < Version::new("10"));
177 }
178
179 #[test]
180 fn zeroes() {
181 assert!(Version::new("01") < Version::new("1"));
182 }
183
184 #[test]
185 fn mix() {
186 assert_eq!(Version::new("Is 01.01.70 the epoch?").collect::<Vec<_>>(),
187 vec![Part::Str("Is "),
188 Part::Num(Number { value: 1, leading_zeroes: 1 }),
189 Part::Str("."),
190 Part::Num(Number { value: 1, leading_zeroes: 1 }),
191 Part::Str("."),
192 Part::Num(Number { value: 70, leading_zeroes: 0 }),
193 Part::Str(" the epoch?")]);
194 }
195}