From 6a5e4533eb608d51a5cec8b9951662d23a1ca1fd Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Mon, 15 May 2023 09:44:39 +0100 Subject: [PATCH 1/3] human text parsing --- cron/src/parsing.rs | 30 ++++-- cron/src/schedule.rs | 74 ++++++++++++++ cron/src/specifier.rs | 8 +- cron/src/time_unit/days_of_month.rs | 16 +++- cron/src/time_unit/days_of_week.rs | 36 ++++++- cron/src/time_unit/hours.rs | 16 +++- cron/src/time_unit/minutes.rs | 16 +++- cron/src/time_unit/mod.rs | 144 +++++++++++++++++++++++++++- cron/src/time_unit/months.rs | 40 +++++++- cron/src/time_unit/seconds.rs | 9 +- cron/src/time_unit/years.rs | 16 +++- 11 files changed, 378 insertions(+), 27 deletions(-) diff --git a/cron/src/parsing.rs b/cron/src/parsing.rs index d2ea84fe5..83f4514fe 100644 --- a/cron/src/parsing.rs +++ b/cron/src/parsing.rs @@ -10,10 +10,10 @@ use std::convert::TryFrom; use std::str::{self, FromStr}; use crate::error::{Error, ErrorKind}; -use crate::schedule::{ScheduleFields, Schedule}; +use crate::ordinal::*; +use crate::schedule::{Schedule, ScheduleFields}; use crate::specifier::*; use crate::time_unit::*; -use crate::ordinal::*; impl FromStr for Schedule { type Err = Error; @@ -52,17 +52,19 @@ where T: TimeUnitField, { fn from_field(field: Field) -> Result { - if field.specifiers.len() == 1 && - field.specifiers.get(0).unwrap() == &RootSpecifier::from(Specifier::All) - { return Ok(T::all()); } - let mut ordinals = OrdinalSet::new(); - for specifier in field.specifiers { - let specifier_ordinals: OrdinalSet = T::ordinals_from_root_specifier(&specifier)?; + if field.specifiers.len() == 1 + && field.specifiers.get(0).unwrap() == &RootSpecifier::from(Specifier::All) + { + return Ok(T::all()); + } + let mut ordinals = OrdinalSet::new(); + for specifier in &field.specifiers { + let specifier_ordinals: OrdinalSet = T::ordinals_from_root_specifier(specifier)?; for ordinal in specifier_ordinals { ordinals.insert(T::validate_ordinal(ordinal)?); } } - Ok(T::from_ordinal_set(ordinals)) + Ok(T::from_ordinal_set(ordinals, field.specifiers)) } } @@ -255,7 +257,15 @@ fn longhand(i: &str) -> IResult<&str, ScheduleFields> { let months = map_res(field, Months::from_field); let days_of_week = map_res(field_with_any, DaysOfWeek::from_field); let years = opt(map_res(field, Years::from_field)); - let fields = tuple((seconds, minutes, hours, days_of_month, months, days_of_week, years)); + let fields = tuple(( + seconds, + minutes, + hours, + days_of_month, + months, + days_of_week, + years, + )); map( terminated(fields, eof), diff --git a/cron/src/schedule.rs b/cron/src/schedule.rs index 846d878c0..d4bc7c482 100644 --- a/cron/src/schedule.rs +++ b/cron/src/schedule.rs @@ -3,6 +3,7 @@ use chrono::{DateTime, Datelike, Timelike}; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::ops::Bound::{Included, Unbounded}; +use crate::error::Error; use crate::ordinal::*; use crate::queries::*; use crate::time_unit::*; @@ -24,6 +25,79 @@ impl Schedule { Schedule { source, fields } } + pub fn to_human_text(&self) -> Result { + let fields = self.fields.clone(); + + let seconds = fields.seconds; + let minutes = fields.minutes; + let hours = fields.hours; + let days_of_week = fields.days_of_week; + let months = fields.months; + let days_of_month = fields.days_of_month; + let years = fields.years; + + let days_anded = days_of_month.is_all() || days_of_week.is_all(); + + let s = match !days_of_month.to_human_text()?.is_empty() + && !days_of_week.to_human_text()?.is_empty() + { + false => "".to_owned(), + true => match days_anded { + false => "and".to_owned(), + true => "if it's".to_owned(), + }, + }; + + let time = match seconds.count() == 1 && minutes.count() == 1 && hours.count() == 1 { + true => { + let second = format!("0{}", &seconds.ordinals().iter().next().unwrap()); + let minute = format!("0{}", &minutes.ordinals().iter().next().unwrap()); + let hour = format!("0{}", &hours.ordinals().iter().next().unwrap()); + Some([ + second[second.len() - 2..].to_owned(), + minute[minute.len() - 2..].to_owned(), + hour[hour.len() - 2..].to_owned(), + ]) + } + false => None, + }; + + Ok(match time { + Some(t) => { + format!( + "At {}:{}:{} {} {} {} {} {}", + &t[2], + &t[1], + &t[0], + &days_of_month.to_human_text()?, + &s, + &days_of_week.to_human_text()?, + &months.to_human_text()?, + &years.to_human_text()? + ) + .trim() + .replace("At every", "Every") + + "." + } + None => { + format!( + "At {} {} {} {} {} {} {} {}", + &seconds.to_human_text()?, + &minutes.to_human_text()?, + &hours.to_human_text()?, + &days_of_month.to_human_text()?, + &s, + &days_of_week.to_human_text()?, + &months.to_human_text()?, + &years.to_human_text()? + ) + .trim() + .replace("At every", "Every") + + "." + } + }) + } + pub fn next_after(&self, after: &DateTime) -> Option> where Z: TimeZone, diff --git a/cron/src/specifier.rs b/cron/src/specifier.rs index 4aca7914c..1b7d6a22a 100644 --- a/cron/src/specifier.rs +++ b/cron/src/specifier.rs @@ -1,6 +1,6 @@ use crate::ordinal::*; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum Specifier { All, Point(Ordinal), @@ -15,7 +15,7 @@ pub enum Specifier { // - named range: 'Mon-Thurs/2' // // Without this separation we would end up with invalid combinations such as 'Mon/2' -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum RootSpecifier { Specifier(Specifier), Period(Specifier, u32), @@ -26,4 +26,6 @@ impl From for RootSpecifier { fn from(specifier: Specifier) -> Self { Self::Specifier(specifier) } -} \ No newline at end of file +} + +impl Eq for RootSpecifier {} \ No newline at end of file diff --git a/cron/src/time_unit/days_of_month.rs b/cron/src/time_unit/days_of_month.rs index 82a4e76ee..e67df7842 100644 --- a/cron/src/time_unit/days_of_month.rs +++ b/cron/src/time_unit/days_of_month.rs @@ -1,16 +1,21 @@ +use crate::TimeUnitSpec; +use crate::error::Error; use crate::ordinal::{Ordinal, OrdinalSet}; +use crate::specifier::RootSpecifier; use crate::time_unit::TimeUnitField; use std::borrow::Cow; #[derive(Clone, Debug, Eq)] pub struct DaysOfMonth { ordinals: Option, + field: Vec } impl TimeUnitField for DaysOfMonth { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self { + fn from_optional_ordinal_set(ordinal_set: Option, field: Vec) -> Self { DaysOfMonth { ordinals: ordinal_set, + field } } fn name() -> Cow<'static, str> { @@ -28,6 +33,15 @@ impl TimeUnitField for DaysOfMonth { None => DaysOfMonth::supported_ordinals(), } } + fn to_human_text (&self) -> Result { + match self.is_all() { + true => Ok("".to_owned()), + false => match Self::human_text_from_field(self.field.clone(), false) { + Ok(s) => Ok(format!("on {s}")), + Err(e) => Err(e) + } + } + } } impl PartialEq for DaysOfMonth { diff --git a/cron/src/time_unit/days_of_week.rs b/cron/src/time_unit/days_of_week.rs index e30a53f22..1c83e269b 100644 --- a/cron/src/time_unit/days_of_week.rs +++ b/cron/src/time_unit/days_of_week.rs @@ -1,17 +1,20 @@ -use crate::error::*; +use crate::{error::*, TimeUnitSpec}; use crate::ordinal::{Ordinal, OrdinalSet}; +use crate::specifier::RootSpecifier; use crate::time_unit::TimeUnitField; use std::borrow::Cow; #[derive(Clone, Debug, Eq)] pub struct DaysOfWeek { ordinals: Option, + field: Vec } impl TimeUnitField for DaysOfWeek { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self { + fn from_optional_ordinal_set(ordinal_set: Option, field: Vec) -> Self { DaysOfWeek { ordinals: ordinal_set, + field } } fn name() -> Cow<'static, str> { @@ -43,12 +46,41 @@ impl TimeUnitField for DaysOfWeek { }; Ok(ordinal) } + fn name_from_ordinal(ordinal: Ordinal) -> Result { + //TODO: Use phf crate + let name = match ordinal { + 1 => "Sunday", + 2 => "Monday", + 3 => "Tuesday", + 4 => "Wednesday", + 5 => "Thursday", + 6 => "Friday", + 7 => "Saturday", + _ => { + return Err(ErrorKind::Expression(format!( + "'{}' is not a valid day of the week.", + ordinal + )) + .into()) + } + }; + Ok(name.to_owned()) + } fn ordinals(&self) -> OrdinalSet { match self.ordinals.clone() { Some(ordinal_set) => ordinal_set, None => DaysOfWeek::supported_ordinals(), } } + fn to_human_text (&self) -> Result { + match self.is_all() { + true => Ok("".to_owned()), + false => match Self::human_text_from_field(self.field.clone(), true) { + Ok(s) => Ok(format!("on {s}")), + Err(e) => Err(e) + } + } + } } impl PartialEq for DaysOfWeek { diff --git a/cron/src/time_unit/hours.rs b/cron/src/time_unit/hours.rs index cda6ecd0d..ad064f719 100644 --- a/cron/src/time_unit/hours.rs +++ b/cron/src/time_unit/hours.rs @@ -1,16 +1,21 @@ +use crate::TimeUnitSpec; +use crate::error::Error; use crate::ordinal::{Ordinal, OrdinalSet}; +use crate::specifier::RootSpecifier; use crate::time_unit::TimeUnitField; use std::borrow::Cow; #[derive(Clone, Debug, Eq)] pub struct Hours { ordinals: Option, + field: Vec, } impl TimeUnitField for Hours { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self { + fn from_optional_ordinal_set(ordinal_set: Option, field: Vec) -> Self { Hours { ordinals: ordinal_set, + field, } } fn name() -> Cow<'static, str> { @@ -28,6 +33,15 @@ impl TimeUnitField for Hours { None => Hours::supported_ordinals(), } } + fn to_human_text (&self) -> Result { + match self.is_all() { + true => Ok("".to_owned()), + false => match Self::human_text_from_field(self.field.clone(), false) { + Ok(s) => Ok(format!("past {s}")), + Err(e) => Err(e) + } + } + } } impl PartialEq for Hours { diff --git a/cron/src/time_unit/minutes.rs b/cron/src/time_unit/minutes.rs index 79afb1ad8..bc8ab395d 100644 --- a/cron/src/time_unit/minutes.rs +++ b/cron/src/time_unit/minutes.rs @@ -1,16 +1,21 @@ +use crate::TimeUnitSpec; +use crate::error::Error; use crate::ordinal::{Ordinal, OrdinalSet}; +use crate::specifier::RootSpecifier; use crate::time_unit::TimeUnitField; use std::borrow::Cow; #[derive(Clone, Debug, Eq)] pub struct Minutes { ordinals: Option, + field: Vec, } impl TimeUnitField for Minutes { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self { + fn from_optional_ordinal_set(ordinal_set: Option, field: Vec) -> Self { Minutes { ordinals: ordinal_set, + field, } } fn name() -> Cow<'static, str> { @@ -28,6 +33,15 @@ impl TimeUnitField for Minutes { None => Minutes::supported_ordinals(), } } + fn to_human_text (&self) -> Result { + match self.is_all() { + true => Ok("".to_owned()), + false => match Self::human_text_from_field(self.field.clone(), false) { + Ok(s) => Ok(format!("past {s}")), + Err(e) => Err(e) + } + } + } } impl PartialEq for Minutes { diff --git a/cron/src/time_unit/mod.rs b/cron/src/time_unit/mod.rs index 460a2298a..4bc7195db 100644 --- a/cron/src/time_unit/mod.rs +++ b/cron/src/time_unit/mod.rs @@ -104,14 +104,21 @@ pub trait TimeUnitField where Self: Sized, { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self; + fn from_optional_ordinal_set( + ordinal_set: Option, + field: Vec, + ) -> Self; fn name() -> Cow<'static, str>; fn inclusive_min() -> Ordinal; fn inclusive_max() -> Ordinal; fn ordinals(&self) -> OrdinalSet; + fn to_human_text(&self) -> Result; fn from_ordinal(ordinal: Ordinal) -> Self { - Self::from_ordinal_set(iter::once(ordinal).collect()) + Self::from_ordinal_set( + iter::once(ordinal).collect(), + vec![RootSpecifier::from(Specifier::Point(ordinal))], + ) } fn supported_ordinals() -> OrdinalSet { @@ -119,11 +126,11 @@ where } fn all() -> Self { - Self::from_optional_ordinal_set(None) + Self::from_optional_ordinal_set(None, vec![RootSpecifier::from(Specifier::All)]) } - fn from_ordinal_set(ordinal_set: OrdinalSet) -> Self { - Self::from_optional_ordinal_set(Some(ordinal_set)) + fn from_ordinal_set(ordinal_set: OrdinalSet, field: Vec) -> Self { + Self::from_optional_ordinal_set(Some(ordinal_set), field) } fn ordinal_from_name(name: &str) -> Result { @@ -136,6 +143,10 @@ where .into()) } + fn name_from_ordinal(ordinal: Ordinal) -> Result { + Ok(ordinal.to_string()) + } + fn validate_ordinal(ordinal: Ordinal) -> Result { //println!("validate_ordinal for {} => {}", Self::name(), ordinal); match ordinal { @@ -218,4 +229,127 @@ where }; Ok(ordinals) } + + fn human_text_from_specifier(specifier: &Specifier) -> Result { + use self::Specifier::*; + //println!("ordinals_from_specifier for {} => {:?}", Self::name(), specifier); + match *specifier { + All => Ok(format!("{}", &Self::name())), + Point(ordinal) => Ok(format!("{}", &Self::name_from_ordinal(ordinal)?)), + Range(start, end) => { + match (Self::validate_ordinal(start), Self::validate_ordinal(end)) { + (Ok(start), Ok(end)) if start <= end => Ok(format!( + "from {} through {}", + &Self::name_from_ordinal(start)?, + &Self::name_from_ordinal(end)? + )), + _ => Err(ErrorKind::Expression(format!( + "Invalid range for {}: {}-{}", + Self::name(), + start, + end + )) + .into()), + } + } + NamedRange(ref start_name, ref end_name) => { + let start = Self::ordinal_from_name(start_name)?; + let end = Self::ordinal_from_name(end_name)?; + match (Self::validate_ordinal(start), Self::validate_ordinal(end)) { + (Ok(start), Ok(end)) if start <= end => Ok(format!( + "from {} through {}", + &Self::name_from_ordinal(start)?, + &Self::name_from_ordinal(end)? + )), + _ => Err(ErrorKind::Expression(format!( + "Invalid named range for {}: {}-{}", + Self::name(), + start_name, + end_name + )) + .into()), + } + } + } + } + + fn human_text_from_root_specifier(root_specifier: &RootSpecifier) -> Result { + let ordinals = match root_specifier { + RootSpecifier::Specifier(specifier) => match specifier { + Specifier::Point(_) => format!("{}", Self::human_text_from_specifier(specifier)?), + _ => format!("every {} {}", Self::name(), Self::human_text_from_specifier(specifier)?), + }, + RootSpecifier::Period(specifier, step) => { + if *step == 0 { + return Err(ErrorKind::Expression(format!("step cannot be 0")).into()); + } + match specifier { + // A point prior to a period implies a range whose start is the specified + // point and terminating inclusively with the inclusive max + Specifier::Point(start) => { + let start = Self::validate_ordinal(*start)?; + format!( + "every {} {} from {} through {}", + step, + &Self::name(), + &Self::name_from_ordinal(start)?, + &Self::inclusive_max() + ) + } + specifier => format!("every {} {}", step, &Self::human_text_from_specifier(specifier)?), + } + } + RootSpecifier::NamedPoint(ref name) => { + // validate name + let ordinal = Self::ordinal_from_name(name)?; + let name = Self::name_from_ordinal(ordinal)?; + format!("{name}") + } + }; + Ok(ordinals) + } + + fn human_text_from_field(field: Vec, named: bool) -> Result { + let mut human_text_list_from_field: Vec = vec![]; + for root_specifier in field { + human_text_list_from_field.push(Self::human_text_from_root_specifier(&root_specifier)?) + } + + let human_text_string = match human_text_list_from_field.len() { + 0 => format!(""), + 1 => format!("{}", human_text_list_from_field[0].to_string()), + 2 => format!( + "{} and {}", + &human_text_list_from_field[0], &human_text_list_from_field[1] + ), + _ => { + format!( + "{}, and {}", + &human_text_list_from_field[0..human_text_list_from_field.len() - 1].join(", "), + &human_text_list_from_field[human_text_list_from_field.len() - 1] + ) + } + }; + let s = match named { + true => "".to_owned(), + false => format!("{} ", &Self::name()), + }; + + Ok(format!("{}{}", &s, &human_text_string) + .replace("every 1st", "every") + .replace(&format!("{} every", &Self::name()), "every") + .replace(&format!(", {}", &Self::name()), ",") + .replace(&format!(", and {}", &Self::name()), ", and")) + } + + fn suffix(num: Ordinal) -> String { + + match num % 10 { + 1 => num.to_string() + "st", + 2 => num.to_string() + "nd", + 3 => num.to_string() + "rd", + _ => num.to_string() + "th", + + } + } } diff --git a/cron/src/time_unit/months.rs b/cron/src/time_unit/months.rs index cb2b02f22..90b854aad 100644 --- a/cron/src/time_unit/months.rs +++ b/cron/src/time_unit/months.rs @@ -1,17 +1,20 @@ -use crate::error::*; +use crate::{error::*, TimeUnitSpec}; use crate::ordinal::{Ordinal, OrdinalSet}; +use crate::specifier::RootSpecifier; use crate::time_unit::TimeUnitField; use std::borrow::Cow; #[derive(Clone, Debug, Eq)] pub struct Months { ordinals: Option, + field: Vec, } impl TimeUnitField for Months { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self { + fn from_optional_ordinal_set(ordinal_set: Option, field: Vec) -> Self { Months { ordinals: ordinal_set, + field, } } fn name() -> Cow<'static, str> { @@ -46,12 +49,45 @@ impl TimeUnitField for Months { }; Ok(ordinal) } + + fn name_from_ordinal(ordinal: Ordinal) -> Result { + //TODO: Use phf crate + let name = match ordinal { + 1 => "January", + 2 => "February", + 3 => "March", + 4 => "April", + 5 => "May", + 6 => "June", + 7 => "July", + 8 => "August", + 9 => "September", + 10 => "October", + 11 => "November", + 12 => "December", + _ => { + return Err( + ErrorKind::Expression(format!("'{}' is not a valid month name.", &ordinal)).into(), + ) + } + }; + Ok(name.to_owned()) + } fn ordinals(&self) -> OrdinalSet { match self.ordinals.clone() { Some(ordinal_set) => ordinal_set, None => Months::supported_ordinals(), } } + fn to_human_text (&self) -> Result { + match self.is_all() { + true => Ok("".to_owned()), + false => match Self::human_text_from_field(self.field.clone(), true) { + Ok(s) => Ok(format!("in {s}")), + Err(e) => Err(e) + } + } + } } impl PartialEq for Months { diff --git a/cron/src/time_unit/seconds.rs b/cron/src/time_unit/seconds.rs index c363a8f87..4bd0b124d 100644 --- a/cron/src/time_unit/seconds.rs +++ b/cron/src/time_unit/seconds.rs @@ -1,16 +1,20 @@ +use crate::error::Error; use crate::ordinal::{Ordinal, OrdinalSet}; +use crate::specifier::RootSpecifier; use crate::time_unit::TimeUnitField; use std::borrow::Cow; #[derive(Clone, Debug, Eq)] pub struct Seconds { ordinals: Option, + field: Vec } impl TimeUnitField for Seconds { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self { + fn from_optional_ordinal_set(ordinal_set: Option, field: Vec) -> Self { Seconds { ordinals: ordinal_set, + field } } fn name() -> Cow<'static, str> { @@ -28,6 +32,9 @@ impl TimeUnitField for Seconds { None => Seconds::supported_ordinals(), } } + fn to_human_text (&self) -> Result { + Self::human_text_from_field(self.field.clone(), false) + } } impl PartialEq for Seconds { diff --git a/cron/src/time_unit/years.rs b/cron/src/time_unit/years.rs index a9050090e..434bd3237 100644 --- a/cron/src/time_unit/years.rs +++ b/cron/src/time_unit/years.rs @@ -1,16 +1,21 @@ +use crate::TimeUnitSpec; +use crate::error::Error; use crate::ordinal::{Ordinal, OrdinalSet}; +use crate::specifier::RootSpecifier; use crate::time_unit::TimeUnitField; use std::borrow::Cow; #[derive(Clone, Debug, Eq)] pub struct Years { ordinals: Option, + field: Vec } impl TimeUnitField for Years { - fn from_optional_ordinal_set(ordinal_set: Option) -> Self { + fn from_optional_ordinal_set(ordinal_set: Option, field: Vec) -> Self { Years { ordinals: ordinal_set, + field } } fn name() -> Cow<'static, str> { @@ -31,6 +36,15 @@ impl TimeUnitField for Years { None => Years::supported_ordinals(), } } + fn to_human_text (&self) -> Result { + match self.is_all() { + true => Ok("".to_owned()), + false => match Self::human_text_from_field(self.field.clone(), false) { + Ok(s) => Ok(format!("in {s}")), + Err(e) => Err(e) + } + } + } } impl PartialEq for Years { From 2d27aa7a69a3111bd0c3900c72706241fd5255cd Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Mon, 15 May 2023 12:50:21 +0100 Subject: [PATCH 2/3] readable names --- cron/src/schedule.rs | 4 ++-- cron/src/time_unit/days_of_month.rs | 3 +++ cron/src/time_unit/days_of_week.rs | 3 +++ cron/src/time_unit/hours.rs | 3 +++ cron/src/time_unit/minutes.rs | 3 +++ cron/src/time_unit/mod.rs | 21 +++++++++++---------- cron/src/time_unit/months.rs | 3 +++ cron/src/time_unit/seconds.rs | 3 +++ cron/src/time_unit/years.rs | 3 +++ 9 files changed, 34 insertions(+), 12 deletions(-) diff --git a/cron/src/schedule.rs b/cron/src/schedule.rs index d4bc7c482..edb3ef344 100644 --- a/cron/src/schedule.rs +++ b/cron/src/schedule.rs @@ -76,7 +76,7 @@ impl Schedule { &years.to_human_text()? ) .trim() - .replace("At every", "Every") + .to_owned() + "." } None => { @@ -92,7 +92,7 @@ impl Schedule { &years.to_human_text()? ) .trim() - .replace("At every", "Every") + .to_owned() + "." } }) diff --git a/cron/src/time_unit/days_of_month.rs b/cron/src/time_unit/days_of_month.rs index e67df7842..2c0f47ab5 100644 --- a/cron/src/time_unit/days_of_month.rs +++ b/cron/src/time_unit/days_of_month.rs @@ -33,6 +33,9 @@ impl TimeUnitField for DaysOfMonth { None => DaysOfMonth::supported_ordinals(), } } + fn name_in_text() -> Cow<'static, str> { + Cow::from("day-of-month") + } fn to_human_text (&self) -> Result { match self.is_all() { true => Ok("".to_owned()), diff --git a/cron/src/time_unit/days_of_week.rs b/cron/src/time_unit/days_of_week.rs index 1c83e269b..491089e74 100644 --- a/cron/src/time_unit/days_of_week.rs +++ b/cron/src/time_unit/days_of_week.rs @@ -72,6 +72,9 @@ impl TimeUnitField for DaysOfWeek { None => DaysOfWeek::supported_ordinals(), } } + fn name_in_text() -> Cow<'static, str> { + Cow::from("day-of-week") + } fn to_human_text (&self) -> Result { match self.is_all() { true => Ok("".to_owned()), diff --git a/cron/src/time_unit/hours.rs b/cron/src/time_unit/hours.rs index ad064f719..7bfac3451 100644 --- a/cron/src/time_unit/hours.rs +++ b/cron/src/time_unit/hours.rs @@ -33,6 +33,9 @@ impl TimeUnitField for Hours { None => Hours::supported_ordinals(), } } + fn name_in_text() -> Cow<'static, str> { + Cow::from("hour") + } fn to_human_text (&self) -> Result { match self.is_all() { true => Ok("".to_owned()), diff --git a/cron/src/time_unit/minutes.rs b/cron/src/time_unit/minutes.rs index bc8ab395d..67b85d1f1 100644 --- a/cron/src/time_unit/minutes.rs +++ b/cron/src/time_unit/minutes.rs @@ -33,6 +33,9 @@ impl TimeUnitField for Minutes { None => Minutes::supported_ordinals(), } } + fn name_in_text() -> Cow<'static, str> { + Cow::from("minute") + } fn to_human_text (&self) -> Result { match self.is_all() { true => Ok("".to_owned()), diff --git a/cron/src/time_unit/mod.rs b/cron/src/time_unit/mod.rs index 4bc7195db..97633d17c 100644 --- a/cron/src/time_unit/mod.rs +++ b/cron/src/time_unit/mod.rs @@ -109,6 +109,7 @@ where field: Vec, ) -> Self; fn name() -> Cow<'static, str>; + fn name_in_text() -> Cow<'static, str>; fn inclusive_min() -> Ordinal; fn inclusive_max() -> Ordinal; fn ordinals(&self) -> OrdinalSet; @@ -234,7 +235,7 @@ where use self::Specifier::*; //println!("ordinals_from_specifier for {} => {:?}", Self::name(), specifier); match *specifier { - All => Ok(format!("{}", &Self::name())), + All => Ok(format!("")), Point(ordinal) => Ok(format!("{}", &Self::name_from_ordinal(ordinal)?)), Range(start, end) => { match (Self::validate_ordinal(start), Self::validate_ordinal(end)) { @@ -277,7 +278,7 @@ where let ordinals = match root_specifier { RootSpecifier::Specifier(specifier) => match specifier { Specifier::Point(_) => format!("{}", Self::human_text_from_specifier(specifier)?), - _ => format!("every {} {}", Self::name(), Self::human_text_from_specifier(specifier)?), + _ => format!("every {} {}", Self::name_in_text(), Self::human_text_from_specifier(specifier)?), }, RootSpecifier::Period(specifier, step) => { if *step == 0 { @@ -290,17 +291,17 @@ where let start = Self::validate_ordinal(*start)?; format!( "every {} {} from {} through {}", - step, - &Self::name(), + &Self::suffix(*step), + &Self::name_in_text(), &Self::name_from_ordinal(start)?, &Self::inclusive_max() ) } - specifier => format!("every {} {}", step, &Self::human_text_from_specifier(specifier)?), + specifier => format!("every {} {}", &Self::suffix(*step), &Self::human_text_from_specifier(specifier)?), } } RootSpecifier::NamedPoint(ref name) => { - // validate name + // validates name let ordinal = Self::ordinal_from_name(name)?; let name = Self::name_from_ordinal(ordinal)?; format!("{name}") @@ -332,14 +333,14 @@ where }; let s = match named { true => "".to_owned(), - false => format!("{} ", &Self::name()), + false => format!("{} ", &Self::name_in_text()), }; Ok(format!("{}{}", &s, &human_text_string) .replace("every 1st", "every") - .replace(&format!("{} every", &Self::name()), "every") - .replace(&format!(", {}", &Self::name()), ",") - .replace(&format!(", and {}", &Self::name()), ", and")) + .replace(&format!("{} every", &Self::name_in_text()), "every") + .replace(&format!(", {}", &Self::name_in_text()), ",") + .replace(&format!(", and {}", &Self::name_in_text()), ", and")) } fn suffix(num: Ordinal) -> String { diff --git a/cron/src/time_unit/months.rs b/cron/src/time_unit/months.rs index 90b854aad..404522932 100644 --- a/cron/src/time_unit/months.rs +++ b/cron/src/time_unit/months.rs @@ -79,6 +79,9 @@ impl TimeUnitField for Months { None => Months::supported_ordinals(), } } + fn name_in_text() -> Cow<'static, str> { + Cow::from("month") + } fn to_human_text (&self) -> Result { match self.is_all() { true => Ok("".to_owned()), diff --git a/cron/src/time_unit/seconds.rs b/cron/src/time_unit/seconds.rs index 4bd0b124d..bcc36d0b3 100644 --- a/cron/src/time_unit/seconds.rs +++ b/cron/src/time_unit/seconds.rs @@ -32,6 +32,9 @@ impl TimeUnitField for Seconds { None => Seconds::supported_ordinals(), } } + fn name_in_text() -> Cow<'static, str> { + Cow::from("second") + } fn to_human_text (&self) -> Result { Self::human_text_from_field(self.field.clone(), false) } diff --git a/cron/src/time_unit/years.rs b/cron/src/time_unit/years.rs index 434bd3237..04ce63d73 100644 --- a/cron/src/time_unit/years.rs +++ b/cron/src/time_unit/years.rs @@ -36,6 +36,9 @@ impl TimeUnitField for Years { None => Years::supported_ordinals(), } } + fn name_in_text() -> Cow<'static, str> { + Cow::from("year") + } fn to_human_text (&self) -> Result { match self.is_all() { true => Ok("".to_owned()), From 36956e0b8ab65ba832e7f001c2ae4a93f39508f7 Mon Sep 17 00:00:00 2001 From: chizor iwuh Date: Mon, 15 May 2023 19:50:40 +0100 Subject: [PATCH 3/3] fix - period parsing --- cron/src/time_unit/mod.rs | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/cron/src/time_unit/mod.rs b/cron/src/time_unit/mod.rs index 97633d17c..8ce6e3659 100644 --- a/cron/src/time_unit/mod.rs +++ b/cron/src/time_unit/mod.rs @@ -113,7 +113,7 @@ where fn inclusive_min() -> Ordinal; fn inclusive_max() -> Ordinal; fn ordinals(&self) -> OrdinalSet; - fn to_human_text(&self) -> Result; + fn to_human_text(&self) -> Result; fn from_ordinal(ordinal: Ordinal) -> Self { Self::from_ordinal_set( @@ -235,12 +235,13 @@ where use self::Specifier::*; //println!("ordinals_from_specifier for {} => {:?}", Self::name(), specifier); match *specifier { - All => Ok(format!("")), + All => Ok(format!("{}", &Self::name_in_text())), Point(ordinal) => Ok(format!("{}", &Self::name_from_ordinal(ordinal)?)), Range(start, end) => { match (Self::validate_ordinal(start), Self::validate_ordinal(end)) { (Ok(start), Ok(end)) if start <= end => Ok(format!( - "from {} through {}", + "{} from {} through {}", + &Self::name_in_text(), &Self::name_from_ordinal(start)?, &Self::name_from_ordinal(end)? )), @@ -258,7 +259,8 @@ where let end = Self::ordinal_from_name(end_name)?; match (Self::validate_ordinal(start), Self::validate_ordinal(end)) { (Ok(start), Ok(end)) if start <= end => Ok(format!( - "from {} through {}", + "{} from {} through {}", + &Self::name_in_text(), &Self::name_from_ordinal(start)?, &Self::name_from_ordinal(end)? )), @@ -278,7 +280,10 @@ where let ordinals = match root_specifier { RootSpecifier::Specifier(specifier) => match specifier { Specifier::Point(_) => format!("{}", Self::human_text_from_specifier(specifier)?), - _ => format!("every {} {}", Self::name_in_text(), Self::human_text_from_specifier(specifier)?), + _ => format!( + "every {}", + Self::human_text_from_specifier(specifier)? + ), }, RootSpecifier::Period(specifier, step) => { if *step == 0 { @@ -297,7 +302,11 @@ where &Self::inclusive_max() ) } - specifier => format!("every {} {}", &Self::suffix(*step), &Self::human_text_from_specifier(specifier)?), + specifier => format!( + "every {} {}", + &Self::suffix(*step), + &Self::human_text_from_specifier(specifier)? + ), } } RootSpecifier::NamedPoint(ref name) => { @@ -344,13 +353,11 @@ where } fn suffix(num: Ordinal) -> String { - - match num % 10 { - 1 => num.to_string() + "st", - 2 => num.to_string() + "nd", - 3 => num.to_string() + "rd", - _ => num.to_string() + "th", - + match num % 10 { + 1 => num.to_string() + "st", + 2 => num.to_string() + "nd", + 3 => num.to_string() + "rd", + _ => num.to_string() + "th", } } }