1use crate::Tag;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeSet;
5use uuid::Uuid;
6
7pub type PatchRef = Uuid;
8type EventRef = String;
9type Set<T> = std::collections::HashSet<T>;
10
11#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "kebab-case")]
13pub struct Patch {
14 pub id: Uuid,
15
16 #[serde(default, skip_serializing_if = "Set::is_empty")]
17 pub add_start: Set<AddStart>,
18
19 #[serde(default, skip_serializing_if = "Set::is_empty")]
20 pub remove_start: Set<RemoveStart>,
21
22 #[serde(default, skip_serializing_if = "Set::is_empty")]
23 pub add_tag: Set<AddTag>,
24
25 #[serde(default, skip_serializing_if = "Set::is_empty")]
26 pub remove_tag: Set<RemoveTag>,
27
28 #[serde(default, skip_serializing_if = "Set::is_empty")]
29 pub create_event: Set<CreateEvent>,
30}
31
32#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
33#[serde(rename_all = "kebab-case")]
34pub struct AddStart {
35 #[serde(default)]
36 pub parents: BTreeSet<PatchRef>,
37 pub event: EventRef,
38 pub time: DateTime<Utc>,
39}
40
41#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "kebab-case")]
43pub struct RemoveStart {
44 #[serde(default)]
45 pub parents: Option<BTreeSet<PatchRef>>,
46 pub patch: PatchRef,
47 pub event: EventRef,
48 pub time: DateTime<Utc>,
49}
50
51#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
52#[serde(rename_all = "kebab-case")]
53pub struct AddTag {
54 #[serde(default)]
55 pub parents: BTreeSet<PatchRef>,
56 pub event: EventRef,
57 pub tag: Tag,
58}
59
60#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "kebab-case")]
62pub struct RemoveTag {
63 #[serde(default)]
64 pub parents: Option<BTreeSet<PatchRef>>,
65 pub patch: PatchRef,
66 pub event: EventRef,
67 pub tag: Tag,
68}
69
70#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
71#[serde(rename_all = "kebab-case")]
72pub struct CreateEvent {
73 pub event: EventRef,
74 pub start: DateTime<Utc>,
75 pub tags: Vec<Tag>,
76}
77
78impl Patch {
79 pub fn new() -> Self {
80 Self {
81 id: Uuid::new_v4(),
82 add_start: Set::new(),
83 remove_start: Set::new(),
84 add_tag: Set::new(),
85 remove_tag: Set::new(),
86 create_event: Set::new(),
87 }
88 }
89
90 pub fn with_id(id: PatchRef) -> Self {
91 Self {
92 id,
93 add_start: Set::new(),
94 remove_start: Set::new(),
95 add_tag: Set::new(),
96 remove_tag: Set::new(),
97 create_event: Set::new(),
98 }
99 }
100
101 pub fn patch_ref(&self) -> &PatchRef {
102 &self.id
103 }
104
105 pub fn parents(&self) -> Set<PatchRef> {
106 let add_start_parents = self.add_start.iter().flat_map(|x| x.parents.iter());
107 let remove_start_parents = self.remove_start.iter().map(|x| &x.patch).chain(
108 self.remove_start
109 .iter()
110 .flat_map(|x| x.parents.iter().flat_map(|s| s.iter())),
111 );
112 let remove_tag_parents = self.remove_tag.iter().map(|x| &x.patch).chain(
113 self.remove_tag
114 .iter()
115 .flat_map(|x| x.parents.iter().flat_map(|s| s.iter())),
116 );
117 let add_tag_parents = self.add_tag.iter().flat_map(|x| x.parents.iter());
118 add_start_parents
119 .chain(remove_start_parents)
120 .chain(remove_tag_parents)
121 .chain(add_tag_parents)
122 .cloned()
123 .collect()
124 }
125
126 pub fn add_start(mut self, parent: PatchRef, event: EventRef, time: DateTime<Utc>) -> Self {
127 self.add_start.insert(AddStart {
128 parents: {
129 let mut s = BTreeSet::new();
130 s.insert(parent);
131 s
132 },
133 event,
134 time,
135 });
136 self
137 }
138
139 pub fn remove_start(mut self, patch: PatchRef, event: EventRef, time: DateTime<Utc>) -> Self {
140 self.remove_start.insert(RemoveStart {
141 parents: None,
142 patch,
143 event,
144 time,
145 });
146 self
147 }
148
149 pub fn add_tag(mut self, parent: PatchRef, event: EventRef, tag: String) -> Self {
150 self.add_tag.insert(AddTag {
151 parents: {
152 let mut s = BTreeSet::new();
153 s.insert(parent);
154 s
155 },
156 event,
157 tag,
158 });
159 self
160 }
161
162 pub fn remove_tag(mut self, patch: PatchRef, event: EventRef, tag: String) -> Self {
163 self.remove_tag.insert(RemoveTag {
164 parents: None,
165 patch,
166 event,
167 tag,
168 });
169 self
170 }
171
172 pub fn create_event(
173 mut self,
174 event: EventRef,
175 start: DateTime<Utc>,
176 tags: Vec<String>,
177 ) -> Self {
178 self.create_event.insert(CreateEvent { event, start, tags });
179 self
180 }
181
182 pub fn insert_add_start(&mut self, add_start: AddStart) {
183 self.add_start.insert(add_start);
184 }
185
186 pub fn insert_remove_start(&mut self, remove_start: RemoveStart) {
187 self.remove_start.insert(remove_start);
188 }
189
190 pub fn insert_add_tag(&mut self, add_tag: AddTag) {
191 self.add_tag.insert(add_tag);
192 }
193
194 pub fn insert_remove_tag(&mut self, remove_tag: RemoveTag) {
195 self.remove_tag.insert(remove_tag);
196 }
197
198 pub fn insert_create_event(&mut self, create_event: CreateEvent) {
199 self.create_event.insert(create_event);
200 }
201}
202
203impl Default for Patch {
204 fn default() -> Self {
205 Patch::new()
206 }
207}
208
209impl AddStart {
210 pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
211 self.parents.iter()
212 }
213}
214impl RemoveStart {
215 pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
216 self.parents.iter().flat_map(|s| s.iter())
217 }
218}
219impl AddTag {
220 pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
221 self.parents.iter()
222 }
223}
224impl RemoveTag {
225 pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
226 self.parents.iter().flat_map(|s| s.iter())
227 }
228}
229
230#[cfg(test)]
231mod test {
232 use super::*;
233 use chrono::offset::{TimeZone, Utc};
234
235 macro_rules! s (
236 { $stuff:expr } => {
237 {
238 $stuff.to_string()
239 }
240 };
241 );
242
243 #[test]
244 fn read_patch_with_create_event_toml() {
245 let id = Uuid::parse_str("e39076fe-6b5a-4a7f-b927-7fc1df5ba275").unwrap();
246 let expected = Patch::with_id(id).create_event(
247 s!("a"),
248 Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
249 vec![s!("work"), s!("coding")],
250 );
251
252 let toml_str = r#"
253 id = "e39076fe-6b5a-4a7f-b927-7fc1df5ba275"
254
255 [[create-event]]
256 event = "a"
257 start = "2019-07-24T14:00:00+00:00"
258 tags = ["work", "coding"]
259 "#;
260 assert_eq!(toml::de::from_str(toml_str), Ok(expected));
261 }
262
263 #[test]
264 fn serialize_patch_with_add_tag_toml() {
265 let id = Uuid::parse_str("e39076fe-6b5a-4a7f-b927-7fc1df5ba275").unwrap();
266 let patch0 = Uuid::parse_str("fa5de1d9-aa11-49fa-b064-8128281a7d91").unwrap();
267 let event0 = Uuid::parse_str("0c435b19-4504-440c-abc7-f4e4d6a7d25f").unwrap();
268
269 let patch = Patch::with_id(id).add_start(
270 patch0.clone(),
271 event0.to_string(),
272 Utc.ymd(2019, 07, 24).and_hms(14, 0, 0),
273 );
274
275 let toml_str = "id = \"e39076fe-6b5a-4a7f-b927-7fc1df5ba275\"\n\n[[add-start]]\nparents = [\"fa5de1d9-aa11-49fa-b064-8128281a7d91\"]\nevent = \"0c435b19-4504-440c-abc7-f4e4d6a7d25f\"\ntime = \"2019-07-24T14:00:00Z\"\n".to_string();
276 let serialized = toml::ser::to_string(&patch).unwrap();
277 println!("{}", serialized);
278 assert_eq!(toml_str, serialized);
279 }
280
281 #[test]
282 fn read_patch_with_parents() {
283 let id = Uuid::parse_str("e39076fe-6b5a-4a7f-b927-7fc1df5ba275").unwrap();
284 let patch0 = Uuid::parse_str("fa5de1d9-aa11-49fa-b064-8128281a7d91").unwrap();
285 let patch1 = Uuid::parse_str("0c435b19-4504-440c-abc7-f4e4d6a7d25f").unwrap();
286
287 let mut expected = Patch::with_id(id);
288
289 let remove_start = RemoveStart {
290 parents: {
291 let mut s = BTreeSet::new();
292 s.insert(patch0.clone());
293 s.insert(patch1.clone());
294 Some(s)
295 },
296 patch: patch0.clone(),
297 event: s!("a"),
298 time: Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
299 };
300 expected.insert_remove_start(remove_start);
301
302 let toml_str = r#"
303 id = "e39076fe-6b5a-4a7f-b927-7fc1df5ba275"
304
305 [[remove-start]]
306 parents = ["fa5de1d9-aa11-49fa-b064-8128281a7d91", "0c435b19-4504-440c-abc7-f4e4d6a7d25f"]
307 patch = "fa5de1d9-aa11-49fa-b064-8128281a7d91"
308 event = "a"
309 time = "2019-07-24T14:00:00+00:00"
310 "#;
311 assert_eq!(toml::de::from_str(toml_str), Ok(expected));
312 }
313
314 #[test]
315 fn read_patch_with_all_fields_toml() {
316 let patch0 = Uuid::parse_str("fa5de1d9-aa11-49fa-b064-8128281a7d91").unwrap();
317
318 let expected =
319 Patch::with_id(Uuid::parse_str("2a226f4d-60f2-493d-9e9a-d6c71d98b515").unwrap())
320 .add_start(
321 patch0.clone(),
322 s!("a"),
323 Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
324 )
325 .remove_start(
326 patch0.clone(),
327 s!("a"),
328 Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
329 )
330 .add_tag(patch0.clone(), s!("a"), s!("work"))
331 .remove_tag(patch0.clone(), s!("a"), s!("coding"))
332 .create_event(
333 s!("a"),
334 Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
335 vec![s!("work"), s!("coding")],
336 );
337
338 let toml_str = r#"
339 id = "2a226f4d-60f2-493d-9e9a-d6c71d98b515"
340
341 [[add-start]]
342 parents = ["fa5de1d9-aa11-49fa-b064-8128281a7d91"]
343 event = "a"
344 time = "2019-07-24T14:00:00+00:00"
345
346 [[remove-start]]
347 patch = "fa5de1d9-aa11-49fa-b064-8128281a7d91"
348 event = "a"
349 time = "2019-07-24T14:00:00+00:00"
350
351 [[add-tag]]
352 parents = ["fa5de1d9-aa11-49fa-b064-8128281a7d91"]
353 event = "a"
354 tag = "work"
355
356 [[remove-tag]]
357 patch = "fa5de1d9-aa11-49fa-b064-8128281a7d91"
358 event = "a"
359 tag = "coding"
360
361 [[create-event]]
362 event = "a"
363 start = "2019-07-24T14:00:00+00:00"
364 tags = ["work", "coding"]
365 "#;
366 assert_eq!(toml::de::from_str(toml_str), Ok(expected));
367 }
368
369}