use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use crate::{ParseErrorKind as EK, Pos};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
enum TorVerStatus {
Other,
Alpha,
Beta,
Rc,
Stable,
}
impl TorVerStatus {
fn suffix(self) -> &'static str {
use TorVerStatus::*;
match self {
Stable => "",
Rc => "-rc",
Beta => "-beta",
Alpha => "-alpha",
Other => "-???",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct TorVersion {
major: u8,
minor: u8,
micro: u8,
patch: u8,
status: TorVerStatus,
dev: bool,
}
impl Display for TorVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let devsuffix = if self.dev { "-dev" } else { "" };
write!(
f,
"{}.{}.{}.{}{}{}",
self.major,
self.minor,
self.micro,
self.patch,
self.status.suffix(),
devsuffix
)
}
}
impl FromStr for TorVersion {
type Err = crate::Error;
fn from_str(s: &str) -> crate::Result<Self> {
let mut parts = s.split('-').fuse();
let ver_part = parts.next();
let status_part = parts.next();
let dev_part = parts.next();
if parts.next().is_some() {
#[allow(clippy::unwrap_used)]
return Err(EK::BadTorVersion.at_pos(Pos::at_end_of(dev_part.unwrap())));
}
let vers: Result<Vec<_>, _> = ver_part
.ok_or_else(|| EK::BadTorVersion.at_pos(Pos::at(s)))?
.splitn(4, '.')
.map(|v| v.parse::<u8>())
.collect();
let vers = vers.map_err(|_| EK::BadTorVersion.at_pos(Pos::at(s)))?;
if vers.len() < 3 {
return Err(EK::BadTorVersion.at_pos(Pos::at(s)));
}
let major = vers[0];
let minor = vers[1];
let micro = vers[2];
let patch = if vers.len() == 4 { vers[3] } else { 0 };
let status = match status_part {
Some("alpha") => TorVerStatus::Alpha,
Some("beta") => TorVerStatus::Beta,
Some("rc") => TorVerStatus::Rc,
None | Some("dev") => TorVerStatus::Stable,
_ => TorVerStatus::Other,
};
let dev = match (status_part, dev_part) {
(_, Some("dev")) => true,
(_, Some(s)) => {
return Err(EK::BadTorVersion.at_pos(Pos::at(s)));
}
(Some("dev"), None) => true,
(_, _) => false,
};
Ok(TorVersion {
major,
minor,
micro,
patch,
status,
dev,
})
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn parse_good() {
let mut lastver = None;
for (s1, s2) in &[
("0.1.2", "0.1.2.0"),
("0.1.2.0-dev", "0.1.2.0-dev"),
("0.4.3.1-bloop", "0.4.3.1-???"),
("0.4.3.1-alpha", "0.4.3.1-alpha"),
("0.4.3.1-alpha-dev", "0.4.3.1-alpha-dev"),
("0.4.3.1-beta", "0.4.3.1-beta"),
("0.4.3.1-rc", "0.4.3.1-rc"),
("0.4.3.1", "0.4.3.1"),
] {
let t: TorVersion = s1.parse().unwrap();
assert_eq!(&t.to_string(), s2);
if let Some(v) = lastver {
assert!(v < t);
}
lastver = Some(t);
}
}
#[test]
fn parse_bad() {
for s in &[
"fred.and.bob",
"11",
"11.22",
"0x2020",
"1.2.3.marzipan",
"0.1.2.5-alpha-deeev",
"0.1.2.5-alpha-dev-turducken",
] {
assert!(s.parse::<TorVersion>().is_err());
}
}
}