vault_tasks_core/
sorter.rs

1use std::cmp::Ordering;
2
3use chrono::NaiveTime;
4use lexical_sort::lexical_cmp;
5use strum::EnumIter;
6use strum_macros::FromRepr;
7
8use super::task::{DueDate, Task};
9
10#[derive(Default, Clone, Copy, FromRepr, EnumIter, strum_macros::Display)]
11pub enum SortingMode {
12    #[default]
13    #[strum(to_string = "Due Date")]
14    ByDueDate,
15    #[strum(to_string = "Title")]
16    ByName,
17}
18
19impl SortingMode {
20    #[must_use]
21    pub fn next(self) -> Self {
22        match self {
23            Self::ByDueDate => Self::ByName,
24            Self::ByName => Self::ByDueDate,
25        }
26    }
27    pub fn sort(tasks: &mut [Task], sorter: Self) {
28        tasks.sort_by(|t1, t2| Self::cmp(t1, t2, sorter));
29    }
30
31    /// Compare two tasks by due date
32    fn cmp_due_date(t1: &Task, t2: &Task) -> Ordering {
33        match (&t1.due_date, &t2.due_date) {
34            (DueDate::Day(d1), DueDate::Day(d2)) => d1.cmp(d2),
35            (DueDate::DayTime(d1), DueDate::DayTime(d2)) => d1.cmp(d2),
36            (DueDate::Day(d1), DueDate::DayTime(d2)) => d1.and_time(NaiveTime::default()).cmp(d2),
37            (DueDate::DayTime(d1), DueDate::Day(d2)) => d1.cmp(&d2.and_time(NaiveTime::default())),
38            (DueDate::NoDate, DueDate::Day(_) | DueDate::DayTime(_)) => Ordering::Greater,
39            (DueDate::Day(_) | DueDate::DayTime(_), DueDate::NoDate) => Ordering::Less,
40            _ => Ordering::Equal,
41        }
42    }
43    /// Compares two tasks with the specified sorting mode
44    /// Sorting mode is used first
45    /// If equal, other attribues will be used:
46    /// - State: `ToDo` < `Done` (in Ord impl of `State`)
47    /// - The other sorting mode
48    /// - Priority: usual number ordering
49    /// - Tags: not used
50    fn cmp(t1: &Task, t2: &Task, sorter: Self) -> Ordering {
51        let res_initial_sort = match sorter {
52            Self::ByDueDate => Self::cmp_due_date(t1, t2),
53            Self::ByName => lexical_cmp(&t1.name, &t2.name),
54        };
55
56        if !matches!(res_initial_sort, Ordering::Equal) {
57            return res_initial_sort;
58        }
59
60        // Compare states
61        let res = t1.state.cmp(&t2.state);
62        if !matches!(res, Ordering::Equal) {
63            return res;
64        }
65
66        // We do the other sorting methods
67        let res = match sorter {
68            Self::ByDueDate => lexical_cmp(&t1.name, &t2.name),
69            Self::ByName => Self::cmp_due_date(t1, t2),
70        };
71        if !matches!(res, Ordering::Equal) {
72            return res;
73        }
74
75        t1.priority.cmp(&t2.priority)
76    }
77}
78#[cfg(test)]
79mod tests {
80
81    use insta::{assert_debug_snapshot, with_settings};
82
83    use super::SortingMode;
84    use crate::{parser::task::parse_task, task::Task, TasksConfig};
85    #[test]
86    fn task_sort_by_name() {
87        let mut source = [
88            "- [ ] test 10/11",
89            "- [ ] test 10/9",
90            "- [ ] test 10/10 p5",
91            "- [ ] test 10/10 10:00",
92            "- [x] zèbre",
93            "- [x] zzz",
94            "- [ ] zzz",
95            "- [ ] test 10/10 p2",
96            "- [x] test",
97            "- [ ] test2",
98            "- [ ] test 10/10 5:00",
99            "- [ ] abc",
100        ];
101        let config = TasksConfig {
102            use_american_format: true,
103            ..Default::default()
104        };
105        let mut tasks: Vec<Task> = source
106            .iter_mut()
107            .map(|input| parse_task(input, String::new(), &config).unwrap())
108            .collect();
109
110        let sorting_mode = SortingMode::ByName;
111        SortingMode::sort(&mut tasks, sorting_mode);
112
113        let tasks = tasks
114            .iter()
115            .map(|task| task.get_fixed_attributes(&config, 2))
116            .collect::<Vec<String>>();
117
118        with_settings!({
119            info=>&source,
120            description => "", // the template source code
121        }, {
122                assert_debug_snapshot!(tasks);
123        });
124    }
125    #[test]
126    fn task_sort_by_due_date() {
127        let mut source = [
128            "- [ ] test 10/11",
129            "- [ ] test 10/9",
130            "- [ ] test 10/10 p5",
131            "- [ ] test 10/10 10:00",
132            "- [x] zèbre",
133            "- [x] zzz",
134            "- [ ] zzz",
135            "- [ ] test 10/10 p2",
136            "- [x] test",
137            "- [ ] test2",
138            "- [ ] test 10/10 5:00",
139            "- [ ] abc",
140        ];
141        let config = TasksConfig {
142            use_american_format: true,
143            ..Default::default()
144        };
145        let mut tasks: Vec<Task> = source
146            .iter_mut()
147            .map(|input| parse_task(input, String::new(), &config).unwrap())
148            .collect();
149
150        let sorting_mode = SortingMode::ByDueDate;
151        SortingMode::sort(&mut tasks, sorting_mode);
152
153        let tasks = tasks
154            .iter()
155            .map(|task| task.get_fixed_attributes(&config, 2))
156            .collect::<Vec<String>>();
157
158        with_settings!({
159            info=>&source,
160            description => "", // the template source code
161        }, {
162                assert_debug_snapshot!(tasks);
163        });
164    }
165}