doing_taskpaper/
serializer.rs1use std::sync::LazyLock;
2
3use regex::Regex;
4
5use crate::Document;
6
7static STRIP_ANSI_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\x1b\[[0-9;]*[A-Za-z]").unwrap());
8
9pub fn serialize(doc: &Document) -> String {
14 strip_ansi(&doc.to_string())
15}
16
17fn strip_ansi(text: &str) -> String {
19 STRIP_ANSI_RE.replace_all(text, "").into_owned()
20}
21
22#[cfg(test)]
23mod test {
24 use chrono::{Local, TimeZone};
25
26 use super::*;
27 use crate::{Entry, Note, Section, Tag, Tags};
28
29 fn sample_date(hour: u32, minute: u32) -> chrono::DateTime<Local> {
30 Local.with_ymd_and_hms(2024, 3, 17, hour, minute, 0).unwrap()
31 }
32
33 mod serialize {
34 use pretty_assertions::assert_eq;
35
36 use super::*;
37
38 #[test]
39 fn it_produces_empty_string_for_empty_document() {
40 let doc = Document::new();
41
42 assert_eq!(serialize(&doc), "");
43 }
44
45 #[test]
46 fn it_round_trips_a_well_formed_document() {
47 let content = "\
48Currently:
49\t- 2024-03-17 14:30 | Working on feature @coding <aaaabbbbccccddddeeeeffffaaaabbbb>
50\t\tA note about the work
51Archive:
52\t- 2024-03-16 10:00 | Old task @done(2024-03-16 11:00) <bbbbccccddddeeeeffffaaaabbbbcccc>";
53 let doc = Document::parse(content);
54
55 let output = serialize(&doc);
56
57 assert_eq!(output, content);
58 }
59
60 #[test]
61 fn it_preserves_duplicate_entries_without_dedup() {
62 let mut doc = Document::new();
63 let entry = Entry::new(
64 sample_date(14, 30),
65 "Task A",
66 Tags::new(),
67 Note::new(),
68 "Currently",
69 Some("aaaabbbbccccddddeeeeffffaaaabbbb"),
70 );
71 let mut s1 = Section::new("Currently");
72 s1.add_entry(entry.clone());
73 let mut s2 = Section::new("Archive");
74 s2.add_entry(entry);
75 doc.add_section(s1);
76 doc.add_section(s2);
77
78 let output = serialize(&doc);
79
80 assert_eq!(output.matches("Task A").count(), 2);
81 }
82
83 #[test]
84 fn it_deduplicates_entries_when_doc_is_deduped_first() {
85 let mut doc = Document::new();
86 let entry = Entry::new(
87 sample_date(14, 30),
88 "Task A",
89 Tags::new(),
90 Note::new(),
91 "Currently",
92 Some("aaaabbbbccccddddeeeeffffaaaabbbb"),
93 );
94 let mut s1 = Section::new("Currently");
95 s1.add_entry(entry.clone());
96 let mut s2 = Section::new("Archive");
97 s2.add_entry(entry);
98 doc.add_section(s1);
99 doc.add_section(s2);
100
101 doc.dedup();
102 let output = serialize(&doc);
103
104 assert_eq!(output.matches("Task A").count(), 1);
105 }
106
107 #[test]
108 fn it_strips_ansi_color_codes() {
109 let mut doc = Document::new();
110 let mut section = Section::new("Currently");
111 section.add_entry(Entry::new(
112 sample_date(14, 30),
113 "\x1b[31mRed task\x1b[0m",
114 Tags::new(),
115 Note::new(),
116 "Currently",
117 Some("aaaabbbbccccddddeeeeffffaaaabbbb"),
118 ));
119 doc.add_section(section);
120
121 let output = serialize(&doc);
122
123 assert!(!output.contains("\x1b["));
124 assert!(output.contains("Red task"));
125 }
126
127 #[test]
128 fn it_preserves_other_content_top() {
129 let mut doc = Document::new();
130 doc.other_content_top_mut().push("# My Doing File".to_string());
131 doc.add_section(Section::new("Currently"));
132
133 let output = serialize(&doc);
134
135 assert!(output.starts_with("# My Doing File\n"));
136 }
137
138 #[test]
139 fn it_preserves_other_content_bottom() {
140 let mut doc = Document::new();
141 doc.add_section(Section::new("Currently"));
142 doc.other_content_bottom_mut().push("# Footer".to_string());
143
144 let output = serialize(&doc);
145
146 assert!(output.ends_with("# Footer"));
147 }
148
149 #[test]
150 fn it_includes_notes() {
151 let mut doc = Document::new();
152 let mut section = Section::new("Currently");
153 section.add_entry(Entry::new(
154 sample_date(14, 30),
155 "Task with notes",
156 Tags::new(),
157 Note::from_text("A note line\nAnother note"),
158 "Currently",
159 Some("aaaabbbbccccddddeeeeffffaaaabbbb"),
160 ));
161 doc.add_section(section);
162
163 let output = serialize(&doc);
164
165 assert!(output.contains("\t\tA note line"));
166 assert!(output.contains("\t\tAnother note"));
167 }
168
169 #[test]
170 fn it_includes_tags() {
171 let mut doc = Document::new();
172 let mut section = Section::new("Currently");
173 section.add_entry(Entry::new(
174 sample_date(14, 30),
175 "Tagged task",
176 Tags::from_iter(vec![
177 Tag::new("coding", None::<String>),
178 Tag::new("done", Some("2024-03-17 15:00")),
179 ]),
180 Note::new(),
181 "Currently",
182 Some("aaaabbbbccccddddeeeeffffaaaabbbb"),
183 ));
184 doc.add_section(section);
185
186 let output = serialize(&doc);
187
188 assert!(output.contains("@coding"));
189 assert!(output.contains("@done(2024-03-17 15:00)"));
190 }
191 }
192
193 mod strip_ansi {
194 use pretty_assertions::assert_eq;
195
196 use super::*;
197
198 #[test]
199 fn it_removes_ansi_escape_sequences() {
200 let input = "\x1b[31mhello\x1b[0m world";
201
202 assert_eq!(strip_ansi(input), "hello world");
203 }
204
205 #[test]
206 fn it_returns_unchanged_string_without_ansi() {
207 let input = "hello world";
208
209 assert_eq!(strip_ansi(input), "hello world");
210 }
211 }
212}