hledger_parser/component/period/
interval.rs1use chumsky::prelude::*;
2
3use crate::{component::whitespace::whitespace, state::State};
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Interval {
7 NthDay(u32),
9 NthWeek(u32),
11 NthQuarter(u32),
13 NthMonth(u32),
15 NthYear(u32),
17 Weekday(chrono::Weekday),
19}
20
21pub fn interval<'a>() -> impl Parser<'a, &'a str, Interval, extra::Full<Rich<'a, char>, State, ()>>
30{
31 let word = choice([
32 just("daily").to(Interval::NthDay(1)),
33 just("weekly").to(Interval::NthWeek(1)),
34 just("biweekly").to(Interval::NthWeek(2)),
35 just("fortnightly").to(Interval::NthWeek(2)),
36 just("monthly").to(Interval::NthMonth(1)),
37 just("bimonthly").to(Interval::NthMonth(2)),
38 just("quarterly").to(Interval::NthQuarter(1)),
39 just("yearly").to(Interval::NthQuarter(1)),
40 ]);
41
42 word.or(every()).or(day_of_week())
43}
44
45fn day_of_week<'a>() -> impl Parser<'a, &'a str, Interval, extra::Full<Rich<'a, char>, State, ()>> {
46 let monday = just("monday")
47 .ignored()
48 .or(just("mon").ignored())
49 .or(just("1st")
50 .then(whitespace().repeated().at_least(1))
51 .then(just("day"))
52 .then(whitespace().repeated().at_least(1))
53 .then(just("of"))
54 .then(whitespace().repeated().at_least(1))
55 .then(just("week"))
56 .ignored())
57 .map(|()| Interval::Weekday(chrono::Weekday::Mon));
58 let tuesday = just("tuesday")
59 .ignored()
60 .or(just("tue").ignored())
61 .or(just("2nd")
62 .then(whitespace().repeated().at_least(1))
63 .then(just("day"))
64 .then(whitespace().repeated().at_least(1))
65 .then(just("of"))
66 .then(whitespace().repeated().at_least(1))
67 .then(just("week"))
68 .ignored())
69 .map(|()| Interval::Weekday(chrono::Weekday::Tue));
70 let wednesday = just("wednesday")
71 .ignored()
72 .or(just("wed").ignored())
73 .or(just("3rd")
74 .then(whitespace().repeated().at_least(1))
75 .then(just("day"))
76 .then(whitespace().repeated().at_least(1))
77 .then(just("of"))
78 .then(whitespace().repeated().at_least(1))
79 .then(just("week"))
80 .ignored())
81 .map(|()| Interval::Weekday(chrono::Weekday::Wed));
82 let thursday = just("thursday")
83 .ignored()
84 .or(just("thu").ignored())
85 .or(just("3rd")
86 .then(whitespace().repeated().at_least(1))
87 .then(just("day"))
88 .then(whitespace().repeated().at_least(1))
89 .then(just("of"))
90 .then(whitespace().repeated().at_least(1))
91 .then(just("week"))
92 .ignored())
93 .map(|()| Interval::Weekday(chrono::Weekday::Thu));
94 let friday = just("friday")
95 .ignored()
96 .or(just("fri").ignored())
97 .or(just("3rd")
98 .then(whitespace().repeated().at_least(1))
99 .then(just("day"))
100 .then(whitespace().repeated().at_least(1))
101 .then(just("of"))
102 .then(whitespace().repeated().at_least(1))
103 .then(just("week"))
104 .ignored())
105 .map(|()| Interval::Weekday(chrono::Weekday::Fri));
106 let saturday = just("saturday")
107 .ignored()
108 .or(just("sat").ignored())
109 .or(just("3rd")
110 .then(whitespace().repeated().at_least(1))
111 .then(just("day"))
112 .then(whitespace().repeated().at_least(1))
113 .then(just("of"))
114 .then(whitespace().repeated().at_least(1))
115 .then(just("week"))
116 .ignored())
117 .map(|()| Interval::Weekday(chrono::Weekday::Sat));
118 let sunday = just("sunday")
119 .ignored()
120 .or(just("sun").ignored())
121 .or(just("3rd")
122 .then(whitespace().repeated().at_least(1))
123 .then(just("day"))
124 .then(whitespace().repeated().at_least(1))
125 .then(just("of"))
126 .then(whitespace().repeated().at_least(1))
127 .then(just("week"))
128 .ignored())
129 .map(|()| Interval::Weekday(chrono::Weekday::Sun));
130 just("every")
131 .then(whitespace().repeated().at_least(1))
132 .ignore_then(
133 monday
134 .or(tuesday)
135 .or(wednesday)
136 .or(thursday)
137 .or(friday)
138 .or(saturday)
139 .or(sunday),
140 )
141}
142
143fn every<'a>() -> impl Parser<'a, &'a str, Interval, extra::Full<Rich<'a, char>, State, ()>> {
144 let every = just("every")
145 .then(whitespace().repeated().at_least(1))
146 .ignore_then(choice([
147 just("day").to(Interval::NthDay(1)),
148 just("week").to(Interval::NthWeek(1)),
149 just("month").to(Interval::NthMonth(1)),
150 just("quarter").to(Interval::NthQuarter(1)),
151 just("year").to(Interval::NthYear(1)),
152 ]));
153 let every_n_days = just("every")
154 .then(whitespace().repeated().at_least(1))
155 .ignore_then(text::int(10).from_str::<u32>().unwrapped())
156 .then_ignore(whitespace().repeated().at_least(1))
157 .then_ignore(just("days"))
158 .map(Interval::NthDay);
159 let every_n_weeks = just("every")
160 .then(whitespace().repeated().at_least(1))
161 .ignore_then(text::int(10).from_str::<u32>().unwrapped())
162 .then_ignore(whitespace().repeated().at_least(1))
163 .then_ignore(just("weeks"))
164 .map(Interval::NthWeek);
165 let every_n_months = just("every")
166 .then(whitespace().repeated().at_least(1))
167 .ignore_then(text::int(10).from_str::<u32>().unwrapped())
168 .then_ignore(whitespace().repeated().at_least(1))
169 .then_ignore(just("months"))
170 .map(Interval::NthMonth);
171 let every_n_quarterd = just("every")
172 .then(whitespace().repeated().at_least(1))
173 .ignore_then(text::int(10).from_str::<u32>().unwrapped())
174 .then_ignore(whitespace().repeated().at_least(1))
175 .then_ignore(just("quarters"))
176 .map(Interval::NthQuarter);
177 let every_n_years = just("every")
178 .then(whitespace().repeated().at_least(1))
179 .ignore_then(text::int(10).from_str::<u32>().unwrapped())
180 .then_ignore(whitespace().repeated().at_least(1))
181 .then_ignore(just("years"))
182 .map(Interval::NthYear);
183 let every_n = every_n_days
184 .or(every_n_weeks)
185 .or(every_n_months)
186 .or(every_n_quarterd)
187 .or(every_n_years);
188 every.or(every_n)
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn every_day() {
197 let result = interval()
198 .then_ignore(end())
199 .parse("every day")
200 .into_result();
201 assert_eq!(result, Ok(Interval::NthDay(1)));
202 }
203
204 #[test]
205 fn every_n_day() {
206 let result = interval()
207 .then_ignore(end())
208 .parse("every 3 days")
209 .into_result();
210 assert_eq!(result, Ok(Interval::NthDay(3)));
211 }
212
213 #[test]
214 fn every_week() {
215 let result = interval()
216 .then_ignore(end())
217 .parse("every week")
218 .into_result();
219 assert_eq!(result, Ok(Interval::NthWeek(1)));
220 }
221
222 #[test]
223 fn every_n_week() {
224 let result = interval()
225 .then_ignore(end())
226 .parse("every 4 weeks")
227 .into_result();
228 assert_eq!(result, Ok(Interval::NthWeek(4)));
229 }
230
231 #[test]
232 fn every_month() {
233 let result = interval()
234 .then_ignore(end())
235 .parse("every month")
236 .into_result();
237 assert_eq!(result, Ok(Interval::NthMonth(1)));
238 }
239
240 #[test]
241 fn every_n_month() {
242 let result = interval()
243 .then_ignore(end())
244 .parse("every 2 months")
245 .into_result();
246 assert_eq!(result, Ok(Interval::NthMonth(2)));
247 }
248
249 #[test]
250 fn every_quarter() {
251 let result = interval()
252 .then_ignore(end())
253 .parse("every quarter")
254 .into_result();
255 assert_eq!(result, Ok(Interval::NthQuarter(1)));
256 }
257
258 #[test]
259 fn every_n_quarter() {
260 let result = interval()
261 .then_ignore(end())
262 .parse("every 2 quarters")
263 .into_result();
264 assert_eq!(result, Ok(Interval::NthQuarter(2)));
265 }
266
267 #[test]
268 fn every_year() {
269 let result = interval()
270 .then_ignore(end())
271 .parse("every year")
272 .into_result();
273 assert_eq!(result, Ok(Interval::NthYear(1)));
274 }
275
276 #[test]
277 fn every_n_years() {
278 let result = interval()
279 .then_ignore(end())
280 .parse("every 10 years")
281 .into_result();
282 assert_eq!(result, Ok(Interval::NthYear(10)));
283 }
284
285 #[test]
286 fn every_weekday() {
287 let result = interval()
288 .then_ignore(end())
289 .parse("every tue")
290 .into_result();
291 assert_eq!(result, Ok(Interval::Weekday(chrono::Weekday::Tue)));
292 }
293}