1use std::hash::Hash;
2
3use doing_config::Config;
4use doing_taskpaper::Entry;
5use doing_time::{DurationFormat, FormattedDuration};
6use indexmap::IndexMap;
7
8pub fn format_interval(entry: &Entry, config: &Config) -> Option<String> {
10 entry.interval().and_then(|iv| {
11 let fmt = DurationFormat::from_config(&config.interval_format);
12 let formatted = FormattedDuration::new(iv, fmt).to_string();
13 if formatted == "00:00:00" { None } else { Some(formatted) }
14 })
15}
16
17pub fn group_entries_by<'a, K, F>(entries: &'a [Entry], key_fn: F) -> Vec<(K, Vec<&'a Entry>)>
19where
20 K: Eq + Hash,
21 F: Fn(&'a Entry) -> K,
22{
23 let mut map: IndexMap<K, Vec<&'a Entry>> = IndexMap::new();
24 for entry in entries {
25 map.entry(key_fn(entry)).or_default().push(entry);
26 }
27 map.into_iter().collect()
28}
29
30pub fn group_by_section(entries: &[Entry]) -> Vec<(&str, Vec<&Entry>)> {
32 group_entries_by(entries, |entry| entry.section())
33}
34
35pub fn note_to_html_list(entry: &Entry, css_class: &str, escape: fn(&str) -> String) -> String {
39 if entry.note().is_empty() {
40 return String::new();
41 }
42
43 let items: Vec<String> = entry
44 .note()
45 .lines()
46 .iter()
47 .map(|line| format!("<li>{}</li>", escape(line.trim())))
48 .collect();
49
50 format!(r#"<ul class="{css_class}">{}</ul>"#, items.join(""))
51}
52
53#[cfg(test)]
54mod test {
55 use doing_taskpaper::{Entry, Note, Tags};
56
57 use super::*;
58 use crate::test_helpers::sample_date;
59
60 mod group_by_section {
61 use pretty_assertions::assert_eq;
62
63 use super::*;
64
65 #[test]
66 fn it_groups_entries_by_section() {
67 let entries = vec![
68 Entry::new(
69 sample_date(17, 14, 0),
70 "A",
71 Tags::new(),
72 Note::new(),
73 "Currently",
74 None::<String>,
75 ),
76 Entry::new(
77 sample_date(17, 15, 0),
78 "B",
79 Tags::new(),
80 Note::new(),
81 "Archive",
82 None::<String>,
83 ),
84 Entry::new(
85 sample_date(17, 16, 0),
86 "C",
87 Tags::new(),
88 Note::new(),
89 "Currently",
90 None::<String>,
91 ),
92 ];
93
94 let groups = group_by_section(&entries);
95
96 assert_eq!(groups.len(), 2);
97 assert_eq!(groups[0].0, "Currently");
98 assert_eq!(groups[0].1.len(), 2);
99 assert_eq!(groups[1].0, "Archive");
100 assert_eq!(groups[1].1.len(), 1);
101 }
102
103 #[test]
104 fn it_preserves_first_seen_order() {
105 let entries = vec![
106 Entry::new(
107 sample_date(17, 14, 0),
108 "A",
109 Tags::new(),
110 Note::new(),
111 "Archive",
112 None::<String>,
113 ),
114 Entry::new(
115 sample_date(17, 15, 0),
116 "B",
117 Tags::new(),
118 Note::new(),
119 "Currently",
120 None::<String>,
121 ),
122 ];
123
124 let groups = group_by_section(&entries);
125
126 assert_eq!(groups[0].0, "Archive");
127 assert_eq!(groups[1].0, "Currently");
128 }
129 }
130}