bartib/data/
getter.rs

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    /* each activity should be placed in the list in the descending order of when it had been
31       started last. To achieve this we reverse the order of the activities before we extract the
32       set of descriptions and activities. Afterwards we also reverse the list of descriptions and
33       activities.
34
35       e.g. if tasks have been started in this order: a, b, c, a, c the list of descriptions and
36           activities should have this order: b, a, c
37    */
38    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}