doing_taskpaper/
section.rs1use std::fmt::{Display, Formatter, Result as FmtResult};
2
3use crate::Entry;
4
5#[derive(Clone, Debug)]
10pub struct Section {
11 entries: Vec<Entry>,
12 title: String,
13}
14
15impl Section {
16 pub fn new(title: impl Into<String>) -> Self {
18 Self {
19 entries: Vec::new(),
20 title: title.into(),
21 }
22 }
23
24 pub fn add_entry(&mut self, entry: Entry) {
26 self.entries.push(entry);
27 }
28
29 pub fn entries(&self) -> &[Entry] {
31 &self.entries
32 }
33
34 pub fn entries_mut(&mut self) -> &mut Vec<Entry> {
36 &mut self.entries
37 }
38
39 pub fn into_entries(self) -> Vec<Entry> {
41 self.entries
42 }
43
44 pub fn is_empty(&self) -> bool {
46 self.entries.is_empty()
47 }
48
49 pub fn len(&self) -> usize {
51 self.entries.len()
52 }
53
54 pub fn remove_entry(&mut self, id: &str) -> usize {
56 let before = self.entries.len();
57 self.entries.retain(|e| e.id() != id);
58 before - self.entries.len()
59 }
60
61 pub fn title(&self) -> &str {
63 &self.title
64 }
65}
66
67impl Display for Section {
68 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
70 write!(f, "{}:", self.title)?;
71 for entry in &self.entries {
72 write!(f, "\n\t- {} | {}", entry.date().format("%Y-%m-%d %H:%M"), entry)?;
73 if !entry.note().is_empty() {
74 write!(f, "\n{}", entry.note())?;
75 }
76 }
77 Ok(())
78 }
79}
80
81#[cfg(test)]
82mod test {
83 use super::*;
84
85 mod display {
86 use chrono::Local;
87 use pretty_assertions::assert_eq;
88
89 use super::*;
90 use crate::{Note, Tags};
91
92 #[test]
93 fn it_formats_empty_section() {
94 let section = Section::new("Currently");
95
96 assert_eq!(format!("{section}"), "Currently:");
97 }
98
99 #[test]
100 fn it_formats_section_with_entries() {
101 let date = Local::now();
102 let formatted_date = date.format("%Y-%m-%d %H:%M");
103 let entry = Entry::new(
104 date,
105 "Working on feature",
106 Tags::new(),
107 Note::new(),
108 "Currently",
109 None::<String>,
110 );
111 let mut section = Section::new("Currently");
112 section.add_entry(entry.clone());
113
114 let output = format!("{section}");
115
116 assert!(output.starts_with("Currently:"));
117 assert!(output.contains(&format!("\t- {formatted_date} | Working on feature")));
118 }
119
120 #[test]
121 fn it_formats_section_with_notes() {
122 let date = Local::now();
123 let entry = Entry::new(
124 date,
125 "Working on feature",
126 Tags::new(),
127 Note::from_str("A note line"),
128 "Currently",
129 None::<String>,
130 );
131 let mut section = Section::new("Currently");
132 section.add_entry(entry);
133
134 let output = format!("{section}");
135
136 assert!(output.contains("\t\tA note line"));
137 }
138 }
139
140 mod is_empty {
141 use chrono::Local;
142 use pretty_assertions::assert_eq;
143
144 use super::*;
145 use crate::{Note, Tags};
146
147 #[test]
148 fn it_returns_true_when_empty() {
149 let section = Section::new("Currently");
150
151 assert_eq!(section.is_empty(), true);
152 }
153
154 #[test]
155 fn it_returns_false_when_not_empty() {
156 let mut section = Section::new("Currently");
157 section.add_entry(Entry::new(
158 Local::now(),
159 "Test",
160 Tags::new(),
161 Note::new(),
162 "Currently",
163 None::<String>,
164 ));
165
166 assert_eq!(section.is_empty(), false);
167 }
168 }
169
170 mod len {
171 use chrono::Local;
172 use pretty_assertions::assert_eq;
173
174 use super::*;
175 use crate::{Note, Tags};
176
177 #[test]
178 fn it_returns_entry_count() {
179 let mut section = Section::new("Currently");
180
181 assert_eq!(section.len(), 0);
182
183 section.add_entry(Entry::new(
184 Local::now(),
185 "First",
186 Tags::new(),
187 Note::new(),
188 "Currently",
189 None::<String>,
190 ));
191 section.add_entry(Entry::new(
192 Local::now(),
193 "Second",
194 Tags::new(),
195 Note::new(),
196 "Currently",
197 None::<String>,
198 ));
199
200 assert_eq!(section.len(), 2);
201 }
202 }
203
204 mod remove_entry {
205 use chrono::Local;
206 use pretty_assertions::assert_eq;
207
208 use super::*;
209 use crate::{Note, Tags};
210
211 #[test]
212 fn it_removes_matching_entry() {
213 let entry = Entry::new(
214 Local::now(),
215 "Test",
216 Tags::new(),
217 Note::new(),
218 "Currently",
219 None::<String>,
220 );
221 let id = entry.id().to_string();
222 let mut section = Section::new("Currently");
223 section.add_entry(entry);
224
225 let removed = section.remove_entry(&id);
226
227 assert_eq!(removed, 1);
228 assert_eq!(section.len(), 0);
229 }
230
231 #[test]
232 fn it_returns_zero_when_no_match() {
233 let mut section = Section::new("Currently");
234 section.add_entry(Entry::new(
235 Local::now(),
236 "Test",
237 Tags::new(),
238 Note::new(),
239 "Currently",
240 None::<String>,
241 ));
242
243 let removed = section.remove_entry("nonexistent");
244
245 assert_eq!(removed, 0);
246 assert_eq!(section.len(), 1);
247 }
248 }
249}