use crate::spec_error::SpecError;
use human_sort::compare;
#[derive(Debug, Default, PartialEq)]
pub enum ParseKind {
#[default]
Unknown,
Req,
Cal,
Sem,
}
#[derive(Debug, Default, PartialEq)]
pub enum ParsePart {
#[default]
Start,
ReqPrefix,
MajorYear,
MinorMonth,
PatchDay,
PreId,
BuildSuffix,
}
impl ParsePart {
pub fn is_prefix(&self) -> bool {
matches!(self, Self::Start | Self::ReqPrefix)
}
pub fn is_suffix(&self) -> bool {
matches!(self, Self::PreId | Self::BuildSuffix)
}
}
#[derive(Debug, Default)]
pub struct UnresolvedParser {
kind: ParseKind,
in_part: ParsePart,
is_and: bool,
req_op: String,
major_year: String,
minor_month: String,
patch_day: String,
pre_id: String,
build_id: String,
results: Vec<String>,
}
impl UnresolvedParser {
pub fn parse(mut self, input: impl AsRef<str>) -> Result<(String, ParseKind), SpecError> {
let input = input.as_ref().trim();
if input.is_empty() || input == "*" {
return Ok(("*".to_owned(), ParseKind::Req));
}
for ch in input.chars() {
match ch {
'=' | '~' | '^' | '>' | '<' => {
if self.in_part != ParsePart::Start && self.in_part != ParsePart::ReqPrefix {
return Err(SpecError::InvalidParseRequirement);
}
self.in_part = ParsePart::ReqPrefix;
self.req_op.push(ch);
}
'*' => {
}
'0'..='9' => {
let part_str = match self.in_part {
ParsePart::Start | ParsePart::ReqPrefix | ParsePart::MajorYear => {
self.in_part = ParsePart::MajorYear;
&mut self.major_year
}
ParsePart::MinorMonth => &mut self.minor_month,
ParsePart::PatchDay => &mut self.patch_day,
ParsePart::PreId => &mut self.pre_id,
ParsePart::BuildSuffix => &mut self.build_id,
};
part_str.push(ch);
}
'a'..='z' | 'A'..='Z' => match self.in_part {
ParsePart::PreId => {
self.pre_id.push(ch);
}
ParsePart::BuildSuffix => {
self.build_id.push(ch);
}
_ => {
if ch == 'v' || ch == 'V' {
continue;
} else {
return Err(SpecError::UnknownParseChar(ch));
}
}
},
'.' | '-' => {
if self.kind == ParseKind::Unknown {
if ch == '-' {
self.kind = ParseKind::Cal;
} else {
self.kind = ParseKind::Sem;
}
}
if ch == '-' {
if self.kind == ParseKind::Sem {
match self.in_part {
ParsePart::MajorYear
| ParsePart::MinorMonth
| ParsePart::PatchDay => {
self.in_part = ParsePart::PreId;
}
ParsePart::PreId => {
self.pre_id.push('-');
}
ParsePart::BuildSuffix => {
self.build_id.push('-');
}
_ => continue,
};
} else if self.kind == ParseKind::Cal {
match self.in_part {
ParsePart::MajorYear => {
self.in_part = ParsePart::MinorMonth;
}
ParsePart::MinorMonth => {
self.in_part = ParsePart::PatchDay;
}
ParsePart::PatchDay | ParsePart::BuildSuffix => {
self.in_part = ParsePart::PreId;
}
ParsePart::PreId => {
self.pre_id.push('-');
}
_ => continue,
};
}
} else if ch == '.' {
if self.kind == ParseKind::Sem {
match self.in_part {
ParsePart::MajorYear => {
self.in_part = ParsePart::MinorMonth;
}
ParsePart::MinorMonth => {
self.in_part = ParsePart::PatchDay;
}
ParsePart::PatchDay => {
self.in_part = ParsePart::PreId;
}
ParsePart::PreId => {
self.pre_id.push('.');
}
ParsePart::BuildSuffix => {
self.build_id.push('.');
}
_ => continue,
};
} else if self.kind == ParseKind::Cal {
match self.in_part {
ParsePart::MajorYear
| ParsePart::MinorMonth
| ParsePart::PatchDay => {
self.in_part = ParsePart::BuildSuffix;
}
ParsePart::PreId => {
self.pre_id.push('.');
}
ParsePart::BuildSuffix => {
self.build_id.push('.');
}
_ => continue,
};
}
}
}
'_' | '+' => {
if ch == '+' {
if self.kind == ParseKind::Sem {
self.in_part = ParsePart::BuildSuffix;
} else {
continue;
}
} else if self.kind == ParseKind::Cal {
self.in_part = ParsePart::BuildSuffix;
} else {
continue;
}
}
',' => {
self.is_and = true;
self.build_result()?;
self.reset_state();
}
' ' => {
if self.in_part.is_prefix() {
} else {
self.is_and = true;
self.build_result()?;
self.reset_state();
}
}
_ => {
return Err(SpecError::UnknownParseChar(ch));
}
}
}
self.build_result()?;
let result = self.get_result();
let is_req = result.contains(',');
Ok((result, if is_req { ParseKind::Req } else { self.kind }))
}
fn get_result(&self) -> String {
self.results.join(",")
}
fn get_part<'p>(&self, value: &'p str) -> &'p str {
let value = value.trim_start_matches('0');
if value.is_empty() {
return "0";
}
value
}
fn build_result(&mut self) -> Result<(), SpecError> {
if self.in_part.is_prefix() {
return Ok(());
}
let mut output = String::new();
let was_calver = self.kind == ParseKind::Cal;
if self.req_op.is_empty() {
if self.minor_month.is_empty() || self.patch_day.is_empty() {
self.kind = ParseKind::Req;
if !self.is_and {
output.push('~');
}
}
} else {
self.kind = ParseKind::Req;
output.push_str(&self.req_op);
}
let separator = if self.kind == ParseKind::Cal && !self.is_and {
'-'
} else {
'.'
};
if was_calver {
let year = self.get_part(&self.major_year);
if year.len() < 4 {
let mut year: usize = year.parse().map_err(|_| SpecError::InvalidYear)?;
year += 2000;
output.push_str(&year.to_string());
} else {
output.push_str(year);
}
} else if self.major_year.is_empty() {
return Err(SpecError::MissingParseMajorPart);
} else {
output.push_str(self.get_part(&self.major_year));
}
if !self.minor_month.is_empty() {
output.push(separator);
output.push_str(self.get_part(&self.minor_month));
}
if !self.patch_day.is_empty() {
output.push(separator);
output.push_str(self.get_part(&self.patch_day));
}
if !self.pre_id.is_empty() {
output.push('-');
output.push_str(&self.pre_id);
}
if !self.build_id.is_empty() {
output.push('+');
output.push_str(&self.build_id);
}
self.results.push(output);
Ok(())
}
fn reset_state(&mut self) {
self.kind = ParseKind::Unknown;
self.in_part = ParsePart::Start;
self.req_op.truncate(0);
self.major_year.truncate(0);
self.minor_month.truncate(0);
self.patch_day.truncate(0);
self.pre_id.truncate(0);
self.build_id.truncate(0);
}
}
pub fn parse_multi(input: impl AsRef<str>) -> Result<Vec<String>, SpecError> {
let input = input.as_ref();
let mut results = vec![];
if input.contains("||") {
let mut parts = input.split("||").map(|p| p.trim()).collect::<Vec<_>>();
parts.sort_by(|a, d| compare(d, a));
for part in parts {
results.push(parse(part)?.0);
}
} else {
results.push(parse(input)?.0);
}
Ok(results)
}
pub fn parse(input: impl AsRef<str>) -> Result<(String, ParseKind), SpecError> {
UnresolvedParser::default().parse(input)
}