1use std::borrow::Cow;
2use std::fmt::{self, Display, Formatter};
3use chrono::{Datelike, NaiveDateTime, Local};
4use serde::{Serialize, Deserialize};
5
6#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
7pub struct Node<'a> {
8 pub id: u64,
9 pub pid: u64,
10 pub depth: usize,
11 pub data: NodeData<'a>,
12 pub splits: Vec<usize>,
13}
14
15#[derive(Serialize, Deserialize)]
16#[cfg_attr(test, derive(Clone, Debug, PartialEq, Default))]
17pub struct NodeData<'a> {
18 pub name: Cow<'a, str>,
19 pub priority: Priority,
20 pub due_date: Option<NaiveDateTime>,
21}
22
23impl<'a> NodeData<'a> {
24 pub fn with_name(name: &'a str) -> Self {
25 NodeData {
26 name: name.into(),
27 priority: Priority::None,
28 due_date: None,
29 }
30 }
31}
32
33#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
34#[cfg_attr(test, derive(Debug, Default))]
35pub enum Priority {
36 High,
37 Medium,
38 Low,
39 #[cfg_attr(test, default)]
40 None,
41}
42
43impl Display for Priority {
44 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
45 match self {
46 Priority::High => write!(f, "High"),
47 Priority::Medium => write!(f, "Medium"),
48 Priority::Low => write!(f, "Low"),
49 _ => Ok(())
50 }
51 }
52}
53
54pub struct Displayable<T: Display>(pub Option<T>);
55
56impl Display for Displayable<NaiveDateTime> {
57 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
58 let Displayable(Some(dt)) = self else { return Ok(()) };
59 let now = Local::now().naive_local();
60
61 if dt.year() != now.year() {
62 write!(f, "{}", dt.format("%e %b %Y %-I:%M %p"))
63 } else if dt.iso_week() != now.iso_week() {
64 write!(f, "{}", dt.format("%e %b %-I:%M %p"))
65 } else if dt.day() != now.day() {
66 write!(f, "{}", dt.format("%A %-I:%M %p"))
67 } else {
68 write!(f, "{}", dt.format("%-I:%M %p"))
69 }
70 }
71}
72
73pub fn wrap_text(text: &str, w: usize) -> Vec<usize> {
74 let mut splits = vec![];
75 let mut i = 0;
76 let mut beg = 0;
77 let mut alt_beg = 0;
78 let mut in_a_word = false;
79 let mut long_word = false;
80 let mut d = 0;
81
82 for (j, (pos, ch)) in text.char_indices().chain(([(text.len(), ' ')]).into_iter()).enumerate() {
83 let diff = (j + d) / w - (i + d) / w;
84 if ch == ' ' {
85 if in_a_word {
86 if j - i == w && !long_word {
87 splits.push(i);
88 d += w - (i + d) % w;
89 }
90
91 if (j + d) % w == 0 {
92 splits.push(pos);
93 i = j;
94 beg = pos;
95 } else if diff > 0 {
96 if !long_word {
97 splits.push(beg);
98 d += w - (i + d) % w;
99 } else {
100 splits.push(alt_beg);
101 }
102 }
103 in_a_word = false;
104 long_word = false;
105 } else {
106 if (j + d) % w == 0 {
107 splits.push(pos);
108 i = j;
109 beg = pos;
110 }
111 }
112 } else {
113 if !in_a_word {
114 if (j + d) % w == 0 {
115 splits.push(pos);
116 }
117 i = j;
118 beg = pos;
119 in_a_word = true;
120 } else {
121 if (j + d) % w == 0 {
122 alt_beg = pos;
123 }
124 if j - i == w {
125 splits.push(alt_beg);
126 i = j;
127 beg = pos;
128 alt_beg = pos;
129 long_word = true;
130 }
131 }
132 }
133 }
134
135 if text.len() > 0 && splits[splits.len() - 1] != text.len() {
136 splits.push(text.len());
137 }
138
139 splits
140}
141
142#[cfg(test)]
143mod tests {
144 use super::wrap_text;
145
146 #[test]
147 fn wrap_text_test() {
148 let expected = &[
149 "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor ",
150 "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis ",
151 "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
152 "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu ",
153 "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in ",
154 "culpa qui officia deserunt mollit anim id est laborum.",
155 ];
156 let text = expected.concat();
157
158 let result: Vec<_> = wrap_text(&text, 80).windows(2).map(|w| &text[w[0]..w[1]]).collect();
159 assert_eq!(result, expected);
160
161 let expected = &[" ", " ", " ", " "];
162 let text = expected.concat();
163
164 let result: Vec<_> = wrap_text(&text, 3).windows(2).map(|w| &text[w[0]..w[1]]).collect();
165 assert_eq!(result, expected);
166
167 let expected = &["###", "###", "###"];
168 let text = expected.concat();
169
170 let result: Vec<_> = wrap_text(&text, 3).windows(2).map(|w| &text[w[0]..w[1]]).collect();
171 assert_eq!(result, expected);
172
173 let expected = &[" ##", "###", "###", "#"];
174 let text = expected.concat();
175
176 let result: Vec<_> = wrap_text(&text, 3).windows(2).map(|w| &text[w[0]..w[1]]).collect();
177 assert_eq!(result, expected);
178
179 let expected = &[" #", "###", "###", "##"];
180 let text = expected.concat();
181
182 let result: Vec<_> = wrap_text(&text, 3).windows(2).map(|w| &text[w[0]..w[1]]).collect();
183 assert_eq!(result, expected);
184
185 let expected = &[" ", "###"];
186 let text = expected.concat();
187
188 let result: Vec<_> = wrap_text(&text, 3).windows(2).map(|w| &text[w[0]..w[1]]).collect();
189 assert_eq!(result, expected);
190
191 let expected = &[" ", "###", " "];
192 let text = expected.concat();
193
194 let result: Vec<_> = wrap_text(&text, 3).windows(2).map(|w| &text[w[0]..w[1]]).collect();
195 assert_eq!(result, expected);
196
197 let expected = &["###", "# ", " ", "###", " ", " ", "###"];
198 let text = expected.concat();
199
200 let result: Vec<_> = wrap_text(&text, 3).windows(2).map(|w| &text[w[0]..w[1]]).collect();
201 assert_eq!(result, expected);
202 }
203}