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