1use std::fmt::{self, Debug};
2use time::{Date, OffsetDateTime, Time, macros::format_description};
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum Skip {
6 Date(Date),
8 DateRange(Date, Date),
10 Day(Vec<u8>),
14 DayRange(usize, usize),
18 Time(Time),
20 TimeRange(Time, Time),
24 None,
26}
27
28impl Default for Skip {
29 fn default() -> Self {
30 Self::None
31 }
32}
33
34impl fmt::Display for Skip {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Skip::Date(date) => write!(f, "date: {date}"),
38 Skip::DateRange(start, end) => write!(f, "date range: {start} - {end}"),
39 Skip::Day(day) => write!(f, "day: {day:?}"),
40 Skip::DayRange(start, end) => write!(f, "day range: {start} - {end}"),
41 Skip::Time(time) => write!(f, "time: {time}"),
42 Skip::TimeRange(start, end) => write!(f, "time range: {start} - {end}"),
43 Skip::None => write!(f, "none"),
44 }
45 }
46}
47
48impl Skip {
49 pub fn is_skip(&self, time: OffsetDateTime) -> bool {
51 match self {
52 Skip::Date(date) => time.date() == *date,
53 Skip::DateRange(start, end) => time.date() >= *start && time.date() <= *end,
54 Skip::Day(day) => day.contains(&(time.weekday().number_from_monday())),
55 Skip::DayRange(start, end) => {
56 let weekday = time.weekday().number_from_monday() as usize;
57 weekday >= *start && weekday <= *end
58 }
59 Skip::Time(skip_time) => time.time() == *skip_time,
60 Skip::TimeRange(start, end) => {
61 let current_time = time.time();
62 if start <= end {
63 current_time >= *start && current_time <= *end
65 } else {
66 current_time >= *start || current_time <= *end
68 }
69 }
70 Skip::None => false,
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
76pub enum Task {
77 Wait(u64, Option<Vec<Skip>>),
79 Interval(u64, Option<Vec<Skip>>),
81 At(Time, Option<Vec<Skip>>),
83 Once(OffsetDateTime, Option<Vec<Skip>>),
85}
86
87impl PartialEq for Task {
88 fn eq(&self, other: &Self) -> bool {
89 match (self, other) {
90 (Task::Wait(a, skip_a), Task::Wait(b, skip_b)) => a == b && skip_a == skip_b,
91 (Task::Interval(a, skip_a), Task::Interval(b, skip_b)) => a == b && skip_a == skip_b,
92 (Task::At(a, skip_a), Task::At(b, skip_b)) => a == b && skip_a == skip_b,
93 (Task::Once(a, skip_a), Task::Once(b, skip_b)) => a == b && skip_a == skip_b,
94 _ => false,
95 }
96 }
97}
98
99impl Task {
100 pub fn parse(s: &str) -> Result<Self, String> {
115 let s = s.trim();
116
117 let open_paren = s.find('(').ok_or_else(|| {
119 format!("Invalid task format: '{s}'. Expected format like 'wait(10)'")
120 })?;
121
122 let close_paren = s
123 .rfind(')')
124 .ok_or_else(|| format!("Missing closing parenthesis in: '{s}'"))?;
125
126 if close_paren <= open_paren {
127 return Err(format!("Invalid parentheses in: '{s}'"));
128 }
129
130 let function_name = s[..open_paren].trim();
131 let args = s[open_paren + 1..close_paren].trim();
132
133 match function_name {
134 "wait" => {
135 let seconds = args
136 .parse::<u64>()
137 .map_err(|_| format!("Invalid seconds value '{args}' in wait({args})"))?;
138 Ok(Task::Wait(seconds, None))
139 }
140 "interval" => {
141 let seconds = args
142 .parse::<u64>()
143 .map_err(|_| format!("Invalid seconds value '{args}' in interval({args})"))?;
144 Ok(Task::Interval(seconds, None))
145 }
146 "at" => {
147 let format = format_description!("[hour]:[minute]");
148 let time = Time::parse(args, &format).map_err(|_| {
149 format!("Invalid time format '{args}' in at({args}). Expected format: HH:MM")
150 })?;
151 Ok(Task::At(time, None))
152 }
153 "once" => {
154 let format = format_description!(
155 "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory]"
156 );
157 let datetime = OffsetDateTime::parse(args, &format)
158 .map_err(|_| format!("Invalid datetime format '{args}' in once({args}). Expected format: YYYY-MM-DD HH:MM:SS +HH"))?;
159 Ok(Task::Once(datetime, None))
160 }
161 _ => Err(format!(
162 "Unknown task type '{function_name}'. Supported types: wait, interval, at, once"
163 )),
164 }
165 }
166}
167
168impl From<&str> for Task {
169 fn from(s: &str) -> Self {
177 Task::parse(s).unwrap_or_else(|err| {
178 panic!("Failed to parse task from string '{s}': {err}");
179 })
180 }
181}
182
183impl From<String> for Task {
184 fn from(s: String) -> Self {
185 Self::from(s.as_str())
186 }
187}
188
189impl From<&String> for Task {
190 fn from(s: &String) -> Self {
191 Self::from(s.as_str())
192 }
193}
194
195#[macro_export]
196macro_rules! task {
197 (wait $seconds:tt) => {
199 $crate::Task::Wait($seconds, None)
200 };
201 (interval $seconds:tt) => {
202 $crate::Task::Interval($seconds, None)
203 };
204 (at $hour:tt : $minute:tt) => {
205 $crate::Task::At(
206 time::Time::from_hms($hour, $minute, 0).unwrap(),
207 None
208 )
209 };
210
211 (wait $seconds:tt, weekday $day:tt) => {
213 $crate::Task::Wait($seconds, Some(vec![$crate::Skip::Day(vec![$day])]))
214 };
215 (wait $seconds:tt, date $year:tt - $month:tt - $day:tt) => {
216 $crate::Task::Wait($seconds, Some(vec![$crate::Skip::Date(
217 time::Date::from_calendar_date($year, time::Month::try_from($month).unwrap(), $day).unwrap()
218 )]))
219 };
220 (wait $seconds:tt, time $start_h:tt : $start_m:tt .. $end_h:tt : $end_m:tt) => {
221 $crate::Task::Wait($seconds, Some(vec![$crate::Skip::TimeRange(
222 time::Time::from_hms($start_h, $start_m, 0).unwrap(),
223 time::Time::from_hms($end_h, $end_m, 0).unwrap()
224 )]))
225 };
226
227 (interval $seconds:tt, weekday $day:tt) => {
228 $crate::Task::Interval($seconds, Some(vec![$crate::Skip::Day(vec![$day])]))
229 };
230 (interval $seconds:tt, date $year:tt - $month:tt - $day:tt) => {
231 $crate::Task::Interval($seconds, Some(vec![$crate::Skip::Date(
232 time::Date::from_calendar_date($year, time::Month::try_from($month).unwrap(), $day).unwrap()
233 )]))
234 };
235 (interval $seconds:tt, time $start_h:tt : $start_m:tt .. $end_h:tt : $end_m:tt) => {
236 $crate::Task::Interval($seconds, Some(vec![$crate::Skip::TimeRange(
237 time::Time::from_hms($start_h, $start_m, 0).unwrap(),
238 time::Time::from_hms($end_h, $end_m, 0).unwrap()
239 )]))
240 };
241
242 (at $hour:tt : $minute:tt, weekday $day:tt) => {
243 $crate::Task::At(
244 time::Time::from_hms($hour, $minute, 0).unwrap(),
245 Some(vec![$crate::Skip::Day(vec![$day])])
246 )
247 };
248 (at $hour:tt : $minute:tt, date $year:tt - $month:tt - $day:tt) => {
249 $crate::Task::At(
250 time::Time::from_hms($hour, $minute, 0).unwrap(),
251 Some(vec![$crate::Skip::Date(
252 time::Date::from_calendar_date($year, time::Month::try_from($month).unwrap(), $day).unwrap()
253 )])
254 )
255 };
256 (at $hour:tt : $minute:tt, time $start_h:tt : $start_m:tt .. $end_h:tt : $end_m:tt) => {
257 $crate::Task::At(
258 time::Time::from_hms($hour, $minute, 0).unwrap(),
259 Some(vec![$crate::Skip::TimeRange(
260 time::Time::from_hms($start_h, $start_m, 0).unwrap(),
261 time::Time::from_hms($end_h, $end_m, 0).unwrap()
262 )])
263 )
264 };
265
266 (wait $seconds:tt, [$($skip:tt)*]) => {
268 $crate::Task::Wait($seconds, Some($crate::task!(@build_skips $($skip)*)))
269 };
270 (interval $seconds:tt, [$($skip:tt)*]) => {
271 $crate::Task::Interval($seconds, Some($crate::task!(@build_skips $($skip)*)))
272 };
273 (at $hour:tt : $minute:tt, [$($skip:tt)*]) => {
274 $crate::Task::At(
275 time::Time::from_hms($hour, $minute, 0).unwrap(),
276 Some($crate::task!(@build_skips $($skip)*))
277 )
278 };
279
280 (@build_skips) => { vec![] };
282 (@build_skips weekday $day:tt $(, $($rest:tt)*)?) => {
283 {
284 let mut skips = vec![$crate::Skip::Day(vec![$day])];
285 $(skips.extend($crate::task!(@build_skips $($rest)*));)?
286 skips
287 }
288 };
289 (@build_skips date $year:tt - $month:tt - $day:tt $(, $($rest:tt)*)?) => {
290 {
291 let mut skips = vec![$crate::Skip::Date(
292 time::Date::from_calendar_date($year, time::Month::try_from($month).unwrap(), $day).unwrap()
293 )];
294 $(skips.extend($crate::task!(@build_skips $($rest)*));)?
295 skips
296 }
297 };
298 (@build_skips time $start_h:tt : $start_m:tt .. $end_h:tt : $end_m:tt $(, $($rest:tt)*)?) => {
299 {
300 let mut skips = vec![$crate::Skip::TimeRange(
301 time::Time::from_hms($start_h, $start_m, 0).unwrap(),
302 time::Time::from_hms($end_h, $end_m, 0).unwrap()
303 )];
304 $(skips.extend($crate::task!(@build_skips $($rest)*));)?
305 skips
306 }
307 };
308}
309
310impl fmt::Display for Task {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 match self {
313 Task::Wait(wait, skip) => {
314 let skip = skip
315 .clone()
316 .unwrap_or_default()
317 .into_iter()
318 .map(|s| s.to_string())
319 .collect::<Vec<String>>()
320 .join(", ");
321 write!(f, "wait: {wait} {skip}")
322 }
323 Task::Interval(interval, skip) => {
324 let skip = skip
325 .clone()
326 .unwrap_or_default()
327 .into_iter()
328 .map(|s| s.to_string())
329 .collect::<Vec<String>>()
330 .join(", ");
331 write!(f, "interval: {interval} {skip}")
332 }
333 Task::At(time, skip) => {
334 let skip = skip
335 .clone()
336 .unwrap_or_default()
337 .into_iter()
338 .map(|s| s.to_string())
339 .collect::<Vec<String>>()
340 .join(", ");
341 write!(f, "at: {time} {skip}")
342 }
343 Task::Once(time, skip) => {
344 let skip = skip
345 .clone()
346 .unwrap_or_default()
347 .into_iter()
348 .map(|s| s.to_string())
349 .collect::<Vec<String>>()
350 .join(", ");
351 write!(f, "once: {time} {skip}")
352 }
353 }
354 }
355}