use alloc::string::String;
use alloc::vec::Vec;
use crate::parse::CronError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Segment {
All,
Step(u32),
Value(u32),
Range(u32, u32),
RangeStep(u32, u32, u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kind {
Minute,
Hour,
Dom,
Month,
Dow,
}
impl Kind {
const fn bounds(self) -> (u32, u32) {
match self {
Kind::Minute => (0, 59),
Kind::Hour => (0, 23),
Kind::Dom => (1, 31),
Kind::Month => (1, 12),
Kind::Dow => (0, 7),
}
}
const fn display_max(self) -> u32 {
match self {
Kind::Dow => 6,
_ => self.bounds().1,
}
}
}
pub(crate) fn parse_field(raw: &str, kind: Kind) -> Result<Vec<Segment>, CronError> {
if raw.is_empty() {
return Err(CronError::InvalidField(String::from(raw)));
}
raw.split(',').map(|part| parse_part(part, kind)).collect()
}
fn parse_part(part: &str, kind: Kind) -> Result<Segment, CronError> {
let invalid = || CronError::InvalidField(String::from(part));
if let Some((base, step_str)) = part.split_once('/') {
let step: u32 = step_str
.parse()
.map_err(|_| CronError::InvalidStep(String::from(part)))?;
if step == 0 {
return Err(CronError::InvalidStep(String::from(part)));
}
if base == "*" {
return Ok(if step == 1 {
Segment::All
} else {
Segment::Step(step)
});
}
if let Some((a, b)) = base.split_once('-') {
let a = resolve(a, kind).map_err(|_| invalid())?;
let b = resolve(b, kind).map_err(|_| invalid())?;
if a > b {
return Err(invalid());
}
return Ok(Segment::RangeStep(a, b, step));
}
let a = resolve(base, kind).map_err(|_| invalid())?;
return Ok(Segment::RangeStep(a, kind.display_max(), step));
}
if part == "*" {
return Ok(Segment::All);
}
if let Some((a, b)) = part.split_once('-') {
let a = resolve(a, kind).map_err(|_| invalid())?;
let b = resolve(b, kind).map_err(|_| invalid())?;
if a > b {
return Err(invalid());
}
return Ok(Segment::Range(a, b));
}
Ok(Segment::Value(resolve(part, kind).map_err(|_| invalid())?))
}
fn resolve(tok: &str, kind: Kind) -> Result<u32, CronError> {
if tok.is_empty() {
return Err(CronError::InvalidField(String::from(tok)));
}
let raw = match tok.parse::<u32>() {
Ok(n) => n,
Err(_) => {
name_to_num(tok, kind).ok_or_else(|| CronError::InvalidField(String::from(tok)))?
}
};
let (min, max) = kind.bounds();
if raw < min || raw > max {
return Err(CronError::InvalidField(String::from(tok)));
}
Ok(raw)
}
fn name_to_num(tok: &str, kind: Kind) -> Option<u32> {
let lower = tok.to_ascii_lowercase();
let table: &[(&str, &str, u32)] = match kind {
Kind::Month => &[
("jan", "january", 1),
("feb", "february", 2),
("mar", "march", 3),
("apr", "april", 4),
("may", "may", 5),
("jun", "june", 6),
("jul", "july", 7),
("aug", "august", 8),
("sep", "september", 9),
("oct", "october", 10),
("nov", "november", 11),
("dec", "december", 12),
],
Kind::Dow => &[
("sun", "sunday", 0),
("mon", "monday", 1),
("tue", "tuesday", 2),
("wed", "wednesday", 3),
("thu", "thursday", 4),
("fri", "friday", 5),
("sat", "saturday", 6),
],
_ => return None,
};
table
.iter()
.find(|(abbr, full, _)| lower == *abbr || lower == *full)
.map(|(_, _, n)| *n)
}