grus/
node.rs

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}