]>
Commit | Line | Data |
---|---|---|
e05b637c DM |
1 | use std::collections::HashMap; |
2 | ||
547f0c97 | 3 | use anyhow::{Error}; |
e05b637c DM |
4 | use lazy_static::lazy_static; |
5 | ||
6 | use super::time::*; | |
7 | ||
177a2de9 | 8 | use crate::tools::nom::{ |
547f0c97 | 9 | parse_complete_line, parse_u64, parse_error, IResult, |
177a2de9 DM |
10 | }; |
11 | ||
e05b637c | 12 | use nom::{ |
177a2de9 | 13 | error::{context}, |
e05b637c | 14 | bytes::complete::{tag, take_while1}, |
547f0c97 | 15 | combinator::{map_res, opt, recognize}, |
e05b637c DM |
16 | sequence::{pair, preceded, tuple}, |
17 | character::complete::{alpha1, space0, digit1}, | |
18 | multi::separated_nonempty_list, | |
19 | }; | |
20 | ||
e05b637c DM |
21 | lazy_static! { |
22 | pub static ref TIME_SPAN_UNITS: HashMap<&'static str, f64> = { | |
23 | let mut map = HashMap::new(); | |
24 | ||
25 | let second = 1.0; | |
26 | ||
27 | map.insert("seconds", second); | |
28 | map.insert("second", second); | |
29 | map.insert("sec", second); | |
30 | map.insert("s", second); | |
31 | ||
32 | let msec = second / 1000.0; | |
33 | ||
34 | map.insert("msec", msec); | |
35 | map.insert("ms", msec); | |
36 | ||
37 | let usec = msec / 1000.0; | |
38 | ||
39 | map.insert("usec", usec); | |
40 | map.insert("us", usec); | |
41 | map.insert("µs", usec); | |
42 | ||
43 | let nsec = usec / 1000.0; | |
44 | ||
45 | map.insert("nsec", nsec); | |
46 | map.insert("ns", nsec); | |
47 | ||
48 | let minute = second * 60.0; | |
49 | ||
50 | map.insert("minutes", minute); | |
51 | map.insert("minute", minute); | |
52 | map.insert("min", minute); | |
53 | map.insert("m", minute); | |
54 | ||
55 | let hour = minute * 60.0; | |
56 | ||
57 | map.insert("hours", hour); | |
58 | map.insert("hour", hour); | |
59 | map.insert("hr", hour); | |
60 | map.insert("h", hour); | |
61 | ||
62 | let day = hour * 24.0 ; | |
63 | ||
64 | map.insert("days", day); | |
65 | map.insert("day", day); | |
66 | map.insert("d", day); | |
67 | ||
68 | let week = day * 7.0; | |
69 | ||
70 | map.insert("weeks", week); | |
71 | map.insert("week", week); | |
72 | map.insert("w", week); | |
73 | ||
74 | let month = 30.44 * day; | |
75 | ||
76 | map.insert("months", month); | |
77 | map.insert("month", month); | |
78 | map.insert("M", month); | |
79 | ||
80 | let year = 365.25 * day; | |
81 | ||
82 | map.insert("years", year); | |
83 | map.insert("year", year); | |
84 | map.insert("y", year); | |
85 | ||
86 | map | |
87 | }; | |
88 | } | |
7a314d18 DM |
89 | fn parse_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, u32> { |
90 | move |i: &str| { | |
91 | let (i, v) = map_res(recognize(digit1), str::parse)(i)?; | |
92 | if (v as usize) >= max { | |
93 | return Err(parse_error(i, "time value too large")); | |
94 | } | |
95 | Ok((i, v)) | |
96 | } | |
e05b637c DM |
97 | } |
98 | ||
177a2de9 | 99 | fn parse_weekday(i: &str) -> IResult<&str, WeekDays> { |
e05b637c DM |
100 | let (i, text) = alpha1(i)?; |
101 | ||
102 | match text.to_ascii_lowercase().as_str() { | |
103 | "monday" | "mon" => Ok((i, WeekDays::MONDAY)), | |
104 | "tuesday" | "tue" => Ok((i, WeekDays::TUESDAY)), | |
105 | "wednesday" | "wed" => Ok((i, WeekDays::WEDNESDAY)), | |
106 | "thursday" | "thu" => Ok((i, WeekDays::THURSDAY)), | |
107 | "friday" | "fri" => Ok((i, WeekDays::FRIDAY)), | |
108 | "saturday" | "sat" => Ok((i, WeekDays::SATURDAY)), | |
109 | "sunday" | "sun" => Ok((i, WeekDays::SUNDAY)), | |
110 | _ => return Err(parse_error(text, "weekday")), | |
111 | } | |
112 | } | |
113 | ||
114 | fn parse_weekdays_range(i: &str) -> IResult<&str, WeekDays> { | |
115 | let (i, startday) = parse_weekday(i)?; | |
116 | ||
117 | let generate_range = |start, end| { | |
118 | let mut res = 0; | |
119 | let mut pos = start; | |
120 | loop { | |
121 | res |= pos; | |
122 | if pos >= end { break; } | |
123 | pos = pos << 1; | |
124 | } | |
125 | WeekDays::from_bits(res).unwrap() | |
126 | }; | |
127 | ||
128 | if let (i, Some((_, endday))) = opt(pair(tag(".."),parse_weekday))(i)? { | |
129 | let start = startday.bits(); | |
130 | let end = endday.bits(); | |
131 | if start > end { | |
132 | let set1 = generate_range(start, WeekDays::SUNDAY.bits()); | |
133 | let set2 = generate_range(WeekDays::MONDAY.bits(), end); | |
134 | Ok((i, set1 | set2)) | |
135 | } else { | |
136 | Ok((i, generate_range(start, end))) | |
137 | } | |
138 | } else { | |
139 | Ok((i, startday)) | |
140 | } | |
141 | } | |
142 | ||
7a314d18 DM |
143 | fn parse_date_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, DateTimeValue> { |
144 | move |i: &str| { | |
145 | let (i, value) = parse_time_comp(max)(i)?; | |
e05b637c | 146 | |
7a314d18 | 147 | if let (i, Some(end)) = opt(preceded(tag(".."), parse_time_comp(max)))(i)? { |
ce7ab28c DC |
148 | if value > end { |
149 | return Err(parse_error(i, "range start is bigger than end")); | |
150 | } | |
7a314d18 DM |
151 | return Ok((i, DateTimeValue::Range(value, end))) |
152 | } | |
e05b637c | 153 | |
7a314d18 DM |
154 | if i.starts_with("/") { |
155 | let i = &i[1..]; | |
156 | let (i, repeat) = parse_time_comp(max)(i)?; | |
157 | Ok((i, DateTimeValue::Repeated(value, repeat))) | |
158 | } else { | |
159 | Ok((i, DateTimeValue::Single(value))) | |
160 | } | |
e05b637c DM |
161 | } |
162 | } | |
163 | ||
13bed622 | 164 | fn parse_date_time_comp_list(start: u32, max: usize) -> impl Fn(&str) -> IResult<&str, Vec<DateTimeValue>> { |
7a314d18 DM |
165 | move |i: &str| { |
166 | if i.starts_with("*") { | |
13bed622 DC |
167 | let i = &i[1..]; |
168 | if i.starts_with("/") { | |
169 | let (n, repeat) = parse_time_comp(max)(&i[1..])?; | |
170 | if repeat > 0 { | |
171 | return Ok((n, vec![DateTimeValue::Repeated(start, repeat)])); | |
172 | } | |
173 | } | |
174 | return Ok((i, Vec::new())); | |
7a314d18 | 175 | } |
e05b637c | 176 | |
7a314d18 | 177 | separated_nonempty_list(tag(","), parse_date_time_comp(max))(i) |
e05b637c | 178 | } |
e05b637c DM |
179 | } |
180 | ||
181 | fn parse_time_spec(i: &str) -> IResult<&str, (Vec<DateTimeValue>, Vec<DateTimeValue>, Vec<DateTimeValue>)> { | |
182 | ||
183 | let (i, (hour, minute, opt_second)) = tuple(( | |
13bed622 DC |
184 | parse_date_time_comp_list(0, 24), |
185 | preceded(tag(":"), parse_date_time_comp_list(0, 60)), | |
186 | opt(preceded(tag(":"), parse_date_time_comp_list(0, 60))), | |
e05b637c DM |
187 | ))(i)?; |
188 | ||
189 | if let Some(second) = opt_second { | |
190 | Ok((i, (hour, minute, second))) | |
191 | } else { | |
192 | Ok((i, (hour, minute, vec![DateTimeValue::Single(0)]))) | |
193 | } | |
194 | } | |
195 | ||
44055cac DC |
196 | fn parse_date_spec(i: &str) -> IResult<&str, (Vec<DateTimeValue>, Vec<DateTimeValue>, Vec<DateTimeValue>)> { |
197 | ||
198 | // TODO: implement ~ for days (man systemd.time) | |
199 | if let Ok((i, (year, month, day))) = tuple(( | |
13bed622 DC |
200 | parse_date_time_comp_list(0, 2200), // the upper limit for systemd, stay compatible |
201 | preceded(tag("-"), parse_date_time_comp_list(1, 13)), | |
202 | preceded(tag("-"), parse_date_time_comp_list(1, 32)), | |
44055cac DC |
203 | ))(i) { |
204 | Ok((i, (year, month, day))) | |
205 | } else if let Ok((i, (month, day))) = tuple(( | |
13bed622 DC |
206 | parse_date_time_comp_list(1, 13), |
207 | preceded(tag("-"), parse_date_time_comp_list(1, 32)), | |
44055cac DC |
208 | ))(i) { |
209 | Ok((i, (Vec::new(), month, day))) | |
210 | } else { | |
211 | Err(parse_error(i, "invalid date spec")) | |
212 | } | |
213 | } | |
214 | ||
e05b637c | 215 | pub fn parse_calendar_event(i: &str) -> Result<CalendarEvent, Error> { |
547f0c97 | 216 | parse_complete_line("calendar event", i, parse_calendar_event_incomplete) |
e05b637c DM |
217 | } |
218 | ||
219 | fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent> { | |
220 | ||
221 | let mut has_dayspec = false; | |
222 | let mut has_timespec = false; | |
44055cac | 223 | let mut has_datespec = false; |
e05b637c DM |
224 | |
225 | let mut event = CalendarEvent::default(); | |
226 | ||
227 | if i.starts_with(|c: char| char::is_ascii_alphabetic(&c)) { | |
228 | ||
229 | match i { | |
230 | "minutely" => { | |
231 | return Ok(("", CalendarEvent { | |
232 | second: vec![DateTimeValue::Single(0)], | |
233 | ..Default::default() | |
234 | })); | |
235 | } | |
236 | "hourly" => { | |
237 | return Ok(("", CalendarEvent { | |
238 | minute: vec![DateTimeValue::Single(0)], | |
239 | second: vec![DateTimeValue::Single(0)], | |
240 | ..Default::default() | |
241 | })); | |
242 | } | |
243 | "daily" => { | |
244 | return Ok(("", CalendarEvent { | |
245 | hour: vec![DateTimeValue::Single(0)], | |
246 | minute: vec![DateTimeValue::Single(0)], | |
247 | second: vec![DateTimeValue::Single(0)], | |
248 | ..Default::default() | |
249 | })); | |
250 | } | |
956295ce TL |
251 | "weekly" => { |
252 | return Ok(("", CalendarEvent { | |
253 | hour: vec![DateTimeValue::Single(0)], | |
254 | minute: vec![DateTimeValue::Single(0)], | |
255 | second: vec![DateTimeValue::Single(0)], | |
256 | days: WeekDays::MONDAY, | |
257 | ..Default::default() | |
258 | })); | |
259 | } | |
44055cac DC |
260 | "monthly" => { |
261 | return Ok(("", CalendarEvent { | |
262 | hour: vec![DateTimeValue::Single(0)], | |
263 | minute: vec![DateTimeValue::Single(0)], | |
264 | second: vec![DateTimeValue::Single(0)], | |
265 | day: vec![DateTimeValue::Single(1)], | |
266 | ..Default::default() | |
267 | })); | |
268 | } | |
269 | "yearly" | "annually" => { | |
270 | return Ok(("", CalendarEvent { | |
271 | hour: vec![DateTimeValue::Single(0)], | |
272 | minute: vec![DateTimeValue::Single(0)], | |
273 | second: vec![DateTimeValue::Single(0)], | |
274 | day: vec![DateTimeValue::Single(1)], | |
275 | month: vec![DateTimeValue::Single(1)], | |
276 | ..Default::default() | |
277 | })); | |
278 | } | |
279 | "quarterly" => { | |
280 | return Ok(("", CalendarEvent { | |
281 | hour: vec![DateTimeValue::Single(0)], | |
282 | minute: vec![DateTimeValue::Single(0)], | |
283 | second: vec![DateTimeValue::Single(0)], | |
284 | day: vec![DateTimeValue::Single(1)], | |
285 | month: vec![ | |
286 | DateTimeValue::Single(1), | |
287 | DateTimeValue::Single(4), | |
288 | DateTimeValue::Single(7), | |
289 | DateTimeValue::Single(10), | |
290 | ], | |
291 | ..Default::default() | |
292 | })); | |
293 | } | |
294 | "semiannually" | "semi-annually" => { | |
295 | return Ok(("", CalendarEvent { | |
296 | hour: vec![DateTimeValue::Single(0)], | |
297 | minute: vec![DateTimeValue::Single(0)], | |
298 | second: vec![DateTimeValue::Single(0)], | |
299 | day: vec![DateTimeValue::Single(1)], | |
300 | month: vec![ | |
301 | DateTimeValue::Single(1), | |
302 | DateTimeValue::Single(7), | |
303 | ], | |
304 | ..Default::default() | |
305 | })); | |
e05b637c DM |
306 | } |
307 | _ => { /* continue */ } | |
308 | } | |
309 | ||
310 | let (n, range_list) = context( | |
311 | "weekday range list", | |
312 | separated_nonempty_list(tag(","), parse_weekdays_range) | |
313 | )(i)?; | |
314 | ||
315 | has_dayspec = true; | |
316 | ||
317 | i = space0(n)?.0; | |
318 | ||
319 | for range in range_list { event.days.insert(range); } | |
320 | } | |
321 | ||
44055cac DC |
322 | if let (n, Some((year, month, day))) = opt(parse_date_spec)(i)? { |
323 | event.year = year; | |
324 | event.month = month; | |
325 | event.day = day; | |
326 | has_datespec = true; | |
327 | i = space0(n)?.0; | |
328 | } | |
e05b637c DM |
329 | |
330 | if let (n, Some((hour, minute, second))) = opt(parse_time_spec)(i)? { | |
331 | event.hour = hour; | |
332 | event.minute = minute; | |
333 | event.second = second; | |
334 | has_timespec = true; | |
335 | i = n; | |
336 | } else { | |
337 | event.hour = vec![DateTimeValue::Single(0)]; | |
338 | event.minute = vec![DateTimeValue::Single(0)]; | |
339 | event.second = vec![DateTimeValue::Single(0)]; | |
340 | } | |
341 | ||
342 | if !(has_dayspec || has_timespec || has_datespec) { | |
343 | return Err(parse_error(i, "date or time specification")); | |
344 | } | |
345 | ||
346 | Ok((i, event)) | |
347 | } | |
348 | ||
349 | fn parse_time_unit(i: &str) -> IResult<&str, &str> { | |
350 | let (n, text) = take_while1(|c: char| char::is_ascii_alphabetic(&c) || c == 'µ')(i)?; | |
351 | if TIME_SPAN_UNITS.contains_key(&text) { | |
352 | Ok((n, text)) | |
353 | } else { | |
354 | Err(parse_error(text, "time unit")) | |
355 | } | |
356 | } | |
357 | ||
358 | ||
359 | pub fn parse_time_span(i: &str) -> Result<TimeSpan, Error> { | |
547f0c97 | 360 | parse_complete_line("time span", i, parse_time_span_incomplete) |
e05b637c DM |
361 | } |
362 | ||
363 | fn parse_time_span_incomplete(mut i: &str) -> IResult<&str, TimeSpan> { | |
364 | ||
365 | let mut ts = TimeSpan::default(); | |
366 | ||
367 | loop { | |
368 | i = space0(i)?.0; | |
369 | if i.is_empty() { break; } | |
370 | let (n, num) = parse_u64(i)?; | |
371 | i = space0(n)?.0; | |
372 | ||
373 | if let (n, Some(unit)) = opt(parse_time_unit)(i)? { | |
374 | i = n; | |
375 | match unit { | |
376 | "seconds" | "second" | "sec" | "s" => { | |
377 | ts.seconds += num; | |
378 | } | |
379 | "msec" | "ms" => { | |
380 | ts.msec += num; | |
381 | } | |
382 | "usec" | "us" | "µs" => { | |
383 | ts.usec += num; | |
384 | } | |
385 | "nsec" | "ns" => { | |
386 | ts.nsec += num; | |
387 | } | |
388 | "minutes" | "minute" | "min" | "m" => { | |
389 | ts.minutes += num; | |
390 | } | |
391 | "hours" | "hour" | "hr" | "h" => { | |
392 | ts.hours += num; | |
393 | } | |
394 | "days" | "day" | "d" => { | |
395 | ts.days += num; | |
396 | } | |
397 | "weeks" | "week" | "w" => { | |
398 | ts.weeks += num; | |
399 | } | |
400 | "months" | "month" | "M" => { | |
401 | ts.months += num; | |
402 | } | |
403 | "years" | "year" | "y" => { | |
404 | ts.years += num; | |
405 | } | |
406 | _ => return Err(parse_error(unit, "internal error")), | |
407 | } | |
408 | } else { | |
409 | ts.seconds += num; | |
410 | } | |
411 | } | |
412 | ||
413 | Ok((i, ts)) | |
414 | } |