use crate::{DebVersion, Error};
pub(crate) fn parseversion(string: &str) -> Result<DebVersion, Error> {
if !is_ascii(string) {
return Err(Error::NotAscii);
}
let string = string
.trim_start_matches(|c: char| c.is_ascii_whitespace())
.trim_end_matches(|c: char| c.is_ascii_whitespace());
if string.is_empty() {
return Err(Error::EmptyVersion);
}
if string.chars().any(|c| c.is_ascii_whitespace()) {
return Err(Error::EmbeddedSpaces);
}
let (epoch, string) = if let Some(colon) = string.find(':') {
let epoch_str = &string[..colon];
let epoch: u32 = epoch_str
.parse()
.map_err(|_| Error::InvalidEpoch(epoch_str.to_string()))?;
if string[colon + 1..].is_empty() {
return Err(Error::NothingAfterColon);
}
(epoch, &string[colon + 1..])
} else {
(0, string)
};
let version = string;
let (revision, version) = if let Some(hyphen) = version.rfind('-') {
if version[hyphen + 1..].is_empty() {
return Err(Error::EmptyRevision);
}
(Some(&version[hyphen + 1..]), &version[..hyphen])
} else {
(None, version)
};
if version.is_empty() {
return Err(Error::EmptyVersionNumber);
}
if !version.chars().next().unwrap().is_ascii_digit() {
return Err(Error::NonDigitVersion);
}
if version.chars().any(|c| is_invalid_char(c, ".-+~:")) {
return Err(Error::InvalidCharacterInVersion);
}
if let Some(revision) = revision {
if revision.chars().any(|c| is_invalid_char(c, ".+~")) {
return Err(Error::InvalidCharacterInRevision);
}
}
Ok(DebVersion {
epoch,
version: version.to_string(),
revision: revision.map(|r| r.to_string()),
})
}
fn is_invalid_char(char: char, set: &str) -> bool {
!char.is_ascii_digit() && !char.is_ascii_alphanumeric() && !set.contains(char)
}
fn is_ascii(input: &str) -> bool {
input.chars().all(|c| c.is_ascii())
}