1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::io;
#[derive(Clone)]
pub struct Version {
pub tool_name: String,
pub version: semver::Version,
pub hash: String,
pub date: String
}
impl Version {
pub fn parse_rusty_version(line: &str) -> io::Result<Self> {
let mut line = line.trim_end_matches(|ch| "\r\n".contains(ch)).splitn(4, " ");
let tool_name = line.next().ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing tool name"))?;
let semver = line.next().ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing semver"))?;
let hash = line.next().map_or(Ok(""), parse_hash)?;
let date = line.next().map_or(Ok(""), parse_date)?;
let semver = semver::Version::parse(semver).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, format!("invalid version: {}", err)))?;
Ok(Self{
tool_name: tool_name.into(),
version: semver,
hash: hash.into(),
date: date.into(),
})
}
pub fn is_at_least(&self, major: u64, minor: u64, patch: u64) -> bool {
let self_ver = (self.version.major, self.version.minor, self.version.patch);
let check_ver = (major, minor, patch);
if self.version.is_prerelease() {
self_ver > check_ver
} else {
self_ver >= check_ver
}
}
pub fn is_after(&self, major: u64, minor: u64, patch: u64) -> bool {
let self_ver = (self.version.major, self.version.minor, self.version.patch);
let check_ver = (major, minor, patch);
self_ver > check_ver
}
}
impl std::str::FromStr for Version {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::parse_rusty_version(s) }
}
fn parse_hash(hash: &str) -> io::Result<&str> {
if !hash.starts_with("(") {
Err(io::Error::new(io::ErrorKind::InvalidData, format!("expected hash {:?} to be enclosed in a parenthesis", hash)))
} else if hash[1..].chars().any(|ch| !ch.is_ascii_hexdigit()) {
Err(io::Error::new(io::ErrorKind::InvalidData, format!("expected hash {:?} to be exclusively ASCII hexidecimal digits", &hash[1..])))
} else {
Ok(&hash[1..])
}
}
fn parse_date(date: &str) -> io::Result<&str> {
if !date.ends_with(")") {
return Err(io::Error::new(io::ErrorKind::InvalidData, format!("expected date {:?} to be enclosed in a parenthesis", date)));
}
let date = &date[..(date.len()-1)];
if date.chars().any(|ch| !(ch.is_ascii_digit() || ch == '-')) {
Err(io::Error::new(io::ErrorKind::InvalidData, format!("expected date {:?} to be exclusively ASCII digits or '-' separators", date)))
} else {
Ok(date)
}
}