1use chrono::NaiveDate;
2use std::collections::HashSet;
3use wildmatch::WildMatch;
4
5use crate::data::activity;
6use crate::data::activity::Activity;
7use crate::data::bartib_file;
8
9pub struct ActivityFilter<'a> {
10 pub number_of_activities: Option<usize>,
11 pub from_date: Option<NaiveDate>,
12 pub to_date: Option<NaiveDate>,
13 pub date: Option<NaiveDate>,
14 pub project: Option<&'a str>,
15}
16
17#[must_use]
18pub fn get_descriptions_and_projects(
19 file_content: &[bartib_file::Line],
20) -> Vec<(&String, &String)> {
21 let mut activities: Vec<&activity::Activity> = get_activities(file_content).collect();
22 get_descriptions_and_projects_from_activities(&mut activities)
23}
24
25fn get_descriptions_and_projects_from_activities<'a>(
26 activities: &mut [&'a Activity],
27) -> Vec<(&'a String, &'a String)> {
28 activities.sort_by_key(|activity| activity.start);
29
30 activities.reverse();
39
40 let mut known_descriptions_and_projects: HashSet<(&String, &String)> = HashSet::new();
41 let mut descriptions_and_projects: Vec<(&String, &String)> = Vec::new();
42
43 for description_and_project in activities.iter().map(|a| (&a.description, &a.project)) {
44 if !known_descriptions_and_projects.contains(&description_and_project) {
45 known_descriptions_and_projects.insert(description_and_project);
46 descriptions_and_projects.push(description_and_project);
47 }
48 }
49
50 descriptions_and_projects.reverse();
51 descriptions_and_projects
52}
53
54#[must_use]
55pub fn get_running_activities(file_content: &[bartib_file::Line]) -> Vec<&activity::Activity> {
56 get_activities(file_content)
57 .filter(|activity| !activity.is_stopped())
58 .collect()
59}
60
61pub fn get_activities(
62 file_content: &[bartib_file::Line],
63) -> impl Iterator<Item = &activity::Activity> {
64 file_content
65 .iter()
66 .filter_map(|line: &bartib_file::Line| match &line.activity {
67 Ok(activity) => Some(activity),
68 Err(_) => {
69 println!(
70 "Warning: Ignoring line {}. Please see `bartib check` for further information",
71 line.line_number.unwrap_or(0),
72 );
73 None
74 }
75 })
76}
77
78pub fn filter_activities<'a>(
79 activities: Vec<&'a activity::Activity>,
80 filter: &'a ActivityFilter,
81) -> Vec<&'a activity::Activity> {
82 let from_date: NaiveDate;
83 let to_date: NaiveDate;
84
85 if let Some(date) = filter.date {
86 from_date = date;
87 to_date = date;
88 } else {
89 from_date = filter.from_date.unwrap_or(NaiveDate::MIN);
90 to_date = filter.to_date.unwrap_or(NaiveDate::MAX);
91 }
92
93 activities
94 .into_iter()
95 .filter(move |activity| {
96 activity.start.date() >= from_date && activity.start.date() <= to_date
97 })
98 .filter(move |activity| {
99 filter
100 .project
101 .map_or(true, |p| WildMatch::new(p).matches(&activity.project))
102 })
103 .collect()
104}
105
106#[must_use]
107pub fn get_last_activity_by_end(file_content: &[bartib_file::Line]) -> Option<&activity::Activity> {
108 get_activities(file_content)
109 .filter(|activity| activity.is_stopped())
110 .max_by_key(|activity| {
111 activity
112 .end
113 .unwrap_or_else(|| NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap())
114 })
115}
116
117#[must_use]
118pub fn get_last_activity_by_start(
119 file_content: &[bartib_file::Line],
120) -> Option<&activity::Activity> {
121 get_activities(file_content).max_by_key(|activity| activity.start)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn get_descriptions_and_projects_test_simple() {
130 let a1 = activity::Activity::start("p1".to_string(), "d1".to_string(), None);
131 let a2 = activity::Activity::start("p1".to_string(), "d1".to_string(), None);
132 let a3 = activity::Activity::start("p2".to_string(), "d1".to_string(), None);
133 let mut activities = vec![&a1, &a2, &a3];
134
135 let descriptions_and_projects =
136 get_descriptions_and_projects_from_activities(&mut activities);
137
138 assert_eq!(descriptions_and_projects.len(), 2);
139 assert_eq!(
140 *descriptions_and_projects.first().unwrap(),
141 (&"d1".to_string(), &"p1".to_string())
142 );
143 assert_eq!(
144 *descriptions_and_projects.get(1).unwrap(),
145 (&"d1".to_string(), &"p2".to_string())
146 );
147 }
148
149 #[test]
150 fn get_descriptions_and_projects_test_restarted_activity() {
151 let a1 = activity::Activity::start("p1".to_string(), "d1".to_string(), None);
152 let a2 = activity::Activity::start("p2".to_string(), "d1".to_string(), None);
153 let a3 = activity::Activity::start("p1".to_string(), "d1".to_string(), None);
154 let mut activities = vec![&a1, &a2, &a3];
155
156 let descriptions_and_projects =
157 get_descriptions_and_projects_from_activities(&mut activities);
158
159 assert_eq!(descriptions_and_projects.len(), 2);
160 assert_eq!(
161 *descriptions_and_projects.first().unwrap(),
162 (&"d1".to_string(), &"p2".to_string())
163 );
164 assert_eq!(
165 *descriptions_and_projects.get(1).unwrap(),
166 (&"d1".to_string(), &"p1".to_string())
167 );
168 }
169}