1use regex::Regex;
8
9#[doc(inline)]
10use crate::date::DateRange;
11#[doc(inline)]
12use crate::date::RangeParser;
13#[doc(inline)]
14use crate::error::Error;
15use crate::Day;
16
17pub trait DayFilter {
19 fn start(&self) -> String;
21 fn end(&self) -> String;
23 fn filter_day(&self, day: Day) -> Option<Day>;
25}
26
27fn day_with_entries(day: Day) -> Option<Day> { (!day.is_empty()).then_some(day) }
29
30#[derive(Debug)]
32pub struct FilterArgs {
33 range: DateRange,
34 projects: Option<Regex>
35}
36
37fn regex_from_projs(projs: &[&str]) -> crate::Result<Regex> {
39 Regex::new(&projs.join("|")).map_err(|_| Error::BadProjectFilter)
40}
41
42fn make_projects_regex_opt(projs: &[&str]) -> crate::Result<Option<Regex>> {
43 if projs.is_empty() {
44 Ok(None)
45 }
46 else {
47 regex_from_projs(projs).map(|r| Some(r))
48 }
49}
50
51impl FilterArgs {
52 pub fn new(dates: &[String], projs: &[String]) -> crate::Result<Self> {
60 let project_list: Vec<&str> = projs.iter().map(String::as_str).collect();
61
62 let mut date_iter = dates.iter().map(String::as_str);
63 let parser = RangeParser::default();
64 let (range, _token) = parser.parse(&mut date_iter)?;
65
66 Ok(Self { range, projects: make_projects_regex_opt(&project_list)? })
67 }
68
69 fn projects(&self) -> Option<&Regex> { self.projects.as_ref() }
71}
72
73impl DayFilter for FilterArgs {
74 fn start(&self) -> String { self.range.start().to_string() }
76
77 fn end(&self) -> String { self.range.end().to_string() }
79
80 fn filter_day(&self, day: Day) -> Option<Day> {
82 day_with_entries(
83 self.projects()
84 .map(|re| day.filtered_by_project(re))
85 .unwrap_or(day)
86 )
87 }
88}
89
90#[derive(Debug, PartialEq, Eq)]
92pub struct DateRangeArgs {
93 range: DateRange
94}
95
96impl DateRangeArgs {
97 pub fn new(dates: &[String]) -> crate::Result<Self> {
103 let mut date_iter = dates.iter().map(String::as_str);
104 let parser = RangeParser::default();
105 let (range, _token) = parser.parse(&mut date_iter)?;
106
107 Ok(Self { range })
108 }
109
110 pub fn start(&self) -> String { self.range.start().to_string() }
112
113 pub fn end(&self) -> String { self.range.end().to_string() }
115}
116
117impl DayFilter for DateRangeArgs {
118 fn start(&self) -> String { self.start() }
120
121 fn end(&self) -> String { self.end() }
123
124 fn filter_day(&self, day: Day) -> Option<Day> { day_with_entries(day) }
127}
128
129#[cfg(test)]
131impl PartialEq for FilterArgs {
132 fn eq(&self, other: &Self) -> bool {
133 (self.start() == other.start())
134 && (self.end() == other.end())
135 && match (self.projects(), other.projects()) {
136 (None, None) => true,
137 (None, _) | (_, None) => false,
138 (Some(lhs), Some(rhs)) => format!("{lhs:?}") == format!("{rhs:?}")
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use spectral::prelude::*;
146
147 use super::*;
148 use crate::Date;
149
150 #[test]
153 fn test_filter_no_args() {
154 let args = vec![];
155 let expected = FilterArgs {
156 range: DateRange::new(Date::today(), Date::today().succ()),
157 projects: None
158 };
159
160 assert_that!(&FilterArgs::new(&args, &[]))
161 .is_ok()
162 .is_equal_to(&expected);
163 }
164
165 #[test]
166 fn test_filter_just_one_date() {
167 let args = vec!["yesterday".to_string()];
168 let expected = FilterArgs {
169 range: DateRange::new(Date::today().pred(), Date::today()),
170 projects: None
171 };
172
173 assert_that!(&FilterArgs::new(&args, &[]))
174 .is_ok()
175 .is_equal_to(&expected);
176 }
177
178 #[test]
179 fn test_filter_just_two_dates() {
180 let args = vec!["2021-12-01".to_string(), "2021-12-07".to_string()];
181 let expected = FilterArgs {
182 range: DateRange::new(
183 Date::new(2021, 12, 1).unwrap(),
184 Date::new(2021, 12, 8).unwrap()
185 ),
186 projects: None
187 };
188
189 assert_that!(&FilterArgs::new(&args, &[]))
190 .is_ok()
191 .is_equal_to(&expected);
192 }
193
194 #[test]
195 fn test_filter_just_project() {
196 let dates = vec![];
197 let proj = vec!["project1".to_string()];
198 let expected = FilterArgs {
199 range: DateRange::new(Date::today(), Date::today().succ()),
200 projects: Some(Regex::new(r"project1").unwrap())
201 };
202
203 assert_that!(&FilterArgs::new(&dates, &proj))
204 .is_ok()
205 .is_equal_to(&expected);
206 }
207
208 #[test]
209 fn test_filter_just_multiple_projects() {
210 let dates = vec![];
211 let projs = vec![
212 "project1".to_string(),
213 "cleanup".to_string(),
214 "profit".to_string(),
215 ];
216 let expected = FilterArgs {
217 range: DateRange::new(Date::today(), Date::today().succ()),
218 projects: Some(Regex::new(r"project1|cleanup|profit").unwrap())
219 };
220
221 assert_that!(&FilterArgs::new(&dates, &projs))
222 .is_ok()
223 .is_equal_to(&expected);
224 }
225
226 #[test]
227 fn test_filter_start_and_project() {
228 let dates = vec!["2021-12-01".to_string()];
229 let projs = vec!["project1".to_string()];
230 let expected = FilterArgs {
231 range: DateRange::new(
232 Date::new(2021, 12, 1).unwrap(),
233 Date::new(2021, 12, 2).unwrap()
234 ),
235 projects: Some(Regex::new(r"project1").unwrap())
236 };
237
238 assert_that!(&FilterArgs::new(&dates, &projs))
239 .is_ok()
240 .is_equal_to(&expected);
241 }
242
243 #[test]
244 fn test_filter_both_dates_and_project() {
245 let dates = vec!["2021-12-01".to_string(), "2021-12-07".to_string()];
246 let projs = vec!["project1".to_string()];
247 let expected = FilterArgs {
248 range: DateRange::new(
249 Date::new(2021, 12, 1).unwrap(),
250 Date::new(2021, 12, 8).unwrap()
251 ),
252 projects: Some(Regex::new(r"project1").unwrap())
253 };
254
255 assert_that!(&FilterArgs::new(&dates, &projs))
256 .is_ok()
257 .is_equal_to(&expected);
258 }
259
260 #[test]
263 fn test_dates_no_args() {
264 let args = vec![];
265 #[rustfmt::skip]
266 let expected = DateRangeArgs {
267 range: DateRange::new(Date::today(), Date::today().succ())
268 };
269
270 assert_that!(&DateRangeArgs::new(&args))
271 .is_ok()
272 .is_equal_to(&expected);
273 }
274
275 #[test]
276 fn test_dates_just_one_date() {
277 let args = vec!["yesterday".to_string()];
278 #[rustfmt::skip]
279 let expected = DateRangeArgs {
280 range: DateRange::new(Date::today().pred(), Date::today())
281 };
282
283 assert_that!(&DateRangeArgs::new(&args))
284 .is_ok()
285 .is_equal_to(&expected);
286 }
287
288 #[test]
289 fn test_dates_both_dates() {
290 let args = vec!["2021-12-01".to_string(), "2021-12-07".to_string()];
291 let expected = DateRangeArgs {
292 range: DateRange::new(
293 Date::new(2021, 12, 1).unwrap(),
294 Date::new(2021, 12, 8).unwrap()
295 )
296 };
297
298 assert_that!(&DateRangeArgs::new(&args))
299 .is_ok()
300 .is_equal_to(&expected);
301 }
302}