1use std::fmt;
2use std::io::{self, BufRead, Cursor};
3use std::str::FromStr;
4
5use thiserror::Error;
6
7use crate::util::*;
8
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
11pub struct Version {
12 pub major: u64,
14 pub minor: u64,
16 pub patch: u64,
18 pub prerelease: Vec<Identifier>,
20 pub build: Vec<Identifier>,
22}
23
24impl fmt::Display for Version {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
27 if !self.prerelease.is_empty() {
28 write!(f, "-")?;
29 for (i, id) in self.prerelease.iter().enumerate() {
30 if i < self.prerelease.len() - 1 {
31 write!(f, "{}.", id)?;
32 } else {
33 write!(f, "{}", id)?;
34 }
35 }
36 }
37
38 if !self.build.is_empty() {
39 write!(f, "+")?;
40 for (i, id) in self.build.iter().enumerate() {
41 if i < self.build.len() - 1 {
42 write!(f, "{}.", id)?;
43 } else {
44 write!(f, "{}", id)?;
45 }
46 }
47 }
48 Ok(())
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum Identifier {
55 Number(u64),
56 String(String),
58}
59
60impl fmt::Display for Identifier {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 Identifier::Number(num) => num.fmt(f),
64 Identifier::String(st) => st.fmt(f),
65 }
66 }
67}
68macro_rules! id_from_number {
69 ($num:ty) => {
70 impl From<$num> for Identifier {
71 fn from(s: $num) -> Self {
72 Identifier::Number(s as u64)
73 }
74 }
75 };
76}
77
78id_from_number!(u8);
79id_from_number!(i8);
80id_from_number!(u16);
81id_from_number!(i16);
82id_from_number!(u32);
83id_from_number!(i32);
84id_from_number!(u64);
85id_from_number!(i64);
86
87impl Version {
88 pub fn new(major: u64, minor: u64, patch: u64) -> Self {
96 Version {
97 major,
98 minor,
99 patch,
100 prerelease: Vec::new(),
101 build: Vec::new(),
102 }
103 }
104
105 pub fn new_prerelease(major: u64, minor: u64, patch: u64, prerelease: Vec<Identifier>) -> Self {
116 Version {
117 major,
118 minor,
119 patch,
120 prerelease,
121 build: Vec::new(),
122 }
123 }
124
125 pub fn new_build(major: u64, minor: u64, patch: u64, build: Vec<Identifier>) -> Self {
136 Version {
137 major,
138 minor,
139 patch,
140 prerelease: Vec::new(),
141 build,
142 }
143 }
144}
145
146#[derive(Debug, Error)]
164pub enum ParseError {
165 #[error("Invalid input: {:?}", found)]
166 Invalid { found: Option<char> },
167 #[error("Invalid numeric range")]
168 InvalidNumericRange,
169 #[error("Unexpected end of input")]
170 UnexpectedEof,
171 #[error("IO")]
172 Io(#[from] io::Error),
173}
174
175fn parse_numeric_range_loose<R: BufRead>(s: R) -> Result<u64, ParseError> {
177 let raw = take_string_while(s, |b| b.is_ascii_digit())?;
178 raw.parse().map_err(|_| ParseError::InvalidNumericRange)
179}
180
181fn parse_part<R: BufRead>(mut s: R) -> Result<Identifier, ParseError> {
182 let part = take_string_while(&mut s, |b| b.is_ascii_alphanumeric())?;
183 match part.parse::<u64>() {
184 Ok(number) => Ok(number.into()),
185 _ => Ok(Identifier::String(part)),
186 }
187}
188
189fn parse_parts<R: BufRead>(mut s: R) -> Result<Vec<Identifier>, ParseError> {
190 let mut res = Vec::new();
191 loop {
192 if is_eof(&mut s) {
193 break;
194 }
195
196 res.push(parse_part(&mut s)?);
197
198 let next = peek1(&mut s);
199 if next == Some(b'.') {
200 s.consume(1)
201 } else {
202 break;
203 }
204 }
205
206 if res.is_empty() {
207 }
209
210 Ok(res)
211}
212
213impl FromStr for Identifier {
214 type Err = ParseError;
215
216 fn from_str(s: &str) -> Result<Self, Self::Err> {
217 let mut s = Cursor::new(s);
218
219 parse_part(&mut s)
220 }
221}
222
223impl FromStr for Version {
224 type Err = ParseError;
225
226 fn from_str(s: &str) -> Result<Self, Self::Err> {
227 let mut s = Cursor::new(s);
228
229 let mut version = Version::default();
230
231 version.major = parse_numeric_range_loose(&mut s)?;
233 if is_eof(&mut s) {
234 return Ok(version);
235 }
236
237 let next = take1(&mut s).map(|s| s as char);
239 if next != Some('.') {
240 return Err(ParseError::Invalid { found: next });
241 }
242
243 version.minor = parse_numeric_range_loose(&mut s)?;
245 if is_eof(&mut s) {
246 return Ok(version);
247 }
248
249 let next = take1(&mut s).map(|s| s as char);
251 if next != Some('.') {
252 return Err(ParseError::Invalid { found: next });
253 }
254
255 version.patch = parse_numeric_range_loose(&mut s)?;
257 if is_eof(&mut s) {
258 return Ok(version);
259 }
260
261 let mut next = peek1(&mut s).map(|s| s as char);
262 if next == Some('.') {
263 s.consume(1);
264 return Err(ParseError::Invalid { found: next });
265 }
266
267 if next == Some('+') || next == Some('-') {
268 s.consume(1);
269 }
270
271 if next.is_some() && next != Some('+') {
274 version.prerelease = parse_parts(&mut s)?;
275 if is_eof(&mut s) {
276 return Ok(version);
277 }
278
279 next = take1(&mut s).map(|s| s as char);
281 }
282
283 if next == Some('+') {
285 version.build = parse_parts(&mut s)?;
286 if is_eof(&mut s) {
287 return Ok(version);
288 }
289 }
290 Err(ParseError::Invalid { found: next })
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn simple_parsing() {
300 assert_eq!("1.2.3".parse::<Version>().unwrap(), Version::new(1, 2, 3));
301 assert_eq!(
302 "1.2.3-alpha.3".parse::<Version>().unwrap(),
303 Version::new_prerelease(1, 2, 3, vec!["alpha".parse().unwrap(), 3.into()])
304 );
305 assert_eq!(
306 "1.2.3+alpha.3".parse::<Version>().unwrap(),
307 Version::new_build(1, 2, 3, vec!["alpha".parse().unwrap(), 3.into()])
308 );
309
310 assert_eq!(
311 "1.2.3-beta.9+acd.v3.2".parse::<Version>().unwrap(),
312 Version {
313 major: 1,
314 minor: 2,
315 patch: 3,
316 prerelease: vec!["beta".parse().unwrap(), 9.into()],
317 build: vec!["acd".parse().unwrap(), "v3".parse().unwrap(), 2.into()],
318 }
319 );
320 }
321
322 #[test]
323 fn display() {
324 assert_eq!(&Version::new(1, 2, 3).to_string(), "1.2.3");
325 assert_eq!(
326 &Version::new_prerelease(1, 2, 3, vec![0.into(), "alpha".parse().unwrap()]).to_string(),
327 "1.2.3-0.alpha"
328 );
329 assert_eq!(
330 &Version::new_build(1, 2, 3, vec![0.into(), "alpha".parse().unwrap()]).to_string(),
331 "1.2.3+0.alpha"
332 );
333 assert_eq!(
334 &Version {
335 major: 1,
336 minor: 2,
337 patch: 3,
338 prerelease: vec![0.into(), "alpha".parse().unwrap()],
339 build: vec!["bla".parse().unwrap(), 9.into()],
340 }
341 .to_string(),
342 "1.2.3-0.alpha+bla.9"
343 );
344 }
345
346 #[test]
347 fn loose_versions() {
348 assert_eq!(
349 "001.20.0301".parse::<Version>().unwrap(),
350 Version::new(1, 20, 301)
351 );
352
353 assert_eq!(
354 "1.2.3-beta.01".parse::<Version>().unwrap(),
355 Version::new_prerelease(1, 2, 3, vec!["beta".parse().unwrap(), 1.into()])
356 );
357
358 assert_eq!(
359 "1.2.3foo".parse::<Version>().unwrap(),
360 Version::new_prerelease(1, 2, 3, vec!["foo".parse().unwrap()])
361 );
362
363 assert_eq!(
364 "1.2.3foo.8".parse::<Version>().unwrap(),
365 Version::new_prerelease(1, 2, 3, vec!["foo".parse().unwrap(), 8.into()])
366 );
367 }
368
369 #[test]
370 fn invalid_versions() {
371 assert!("1.2.3.8".parse::<Version>().is_err());
372 assert!("HELLO WORLD".parse::<Version>().is_err());
373 assert!("foo.bar.baz".parse::<Version>().is_err());
374 }
375}