1use std::{fmt::Display, num::NonZeroU32, str::FromStr};
6
7use chrono::{DateTime, Local};
8use icalendar::{Component, EventLike};
9
10use crate::LooseDateTime;
11
12pub trait Event {
14 fn short_id(&self) -> Option<NonZeroU32> {
18 None
19 }
20
21 fn uid(&self) -> &str;
23
24 fn description(&self) -> Option<&str>;
26
27 fn start(&self) -> Option<LooseDateTime>;
29
30 fn end(&self) -> Option<LooseDateTime>;
32
33 fn status(&self) -> Option<EventStatus>;
35
36 fn summary(&self) -> &str;
38}
39
40impl Event for icalendar::Event {
41 fn uid(&self) -> &str {
42 self.get_uid().unwrap_or("")
43 }
44
45 fn description(&self) -> Option<&str> {
46 self.get_description()
47 }
48
49 fn start(&self) -> Option<LooseDateTime> {
50 self.get_start().map(Into::into)
51 }
52
53 fn end(&self) -> Option<LooseDateTime> {
54 self.get_end().map(Into::into)
55 }
56
57 fn status(&self) -> Option<EventStatus> {
58 self.get_status().map(EventStatus::from)
59 }
60
61 fn summary(&self) -> &str {
62 self.get_summary().unwrap_or("")
63 }
64}
65
66#[derive(Debug)]
68pub struct EventDraft {
69 pub description: Option<String>,
71
72 pub start: Option<LooseDateTime>,
74
75 pub end: Option<LooseDateTime>,
77
78 pub status: EventStatus,
80
81 pub summary: String,
83}
84
85impl EventDraft {
86 pub(crate) fn default() -> Self {
88 Self {
89 description: None,
90 start: None,
91 end: None,
92 status: EventStatus::default(),
93 summary: String::new(),
94 }
95 }
96
97 pub(crate) fn into_ics(self, uid: &str) -> icalendar::Event {
99 let mut event = icalendar::Event::with_uid(uid);
100
101 if let Some(description) = self.description {
102 Component::description(&mut event, &description);
103 }
104
105 if let Some(start) = self.start {
106 EventLike::starts(&mut event, start);
107 }
108
109 if let Some(end) = self.end {
110 EventLike::ends(&mut event, end);
111 }
112
113 icalendar::Event::status(&mut event, self.status.into());
114
115 Component::summary(&mut event, &self.summary);
116
117 event
118 }
119}
120
121#[derive(Debug, Default, Clone)]
123pub struct EventPatch {
124 pub description: Option<Option<String>>,
126
127 pub start: Option<Option<LooseDateTime>>,
129
130 pub end: Option<Option<LooseDateTime>>,
132
133 pub status: Option<EventStatus>,
135
136 pub summary: Option<String>,
138}
139
140impl EventPatch {
141 pub fn is_empty(&self) -> bool {
143 self.description.is_none()
144 && self.start.is_none()
145 && self.end.is_none()
146 && self.status.is_none()
147 && self.summary.is_none()
148 }
149
150 pub(crate) fn apply_to<'a>(&self, e: &'a mut icalendar::Event) -> &'a mut icalendar::Event {
152 if let Some(description) = &self.description {
153 match description {
154 Some(desc) => e.description(desc),
155 None => e.remove_description(),
156 };
157 }
158
159 if let Some(start) = &self.start {
160 match start {
161 Some(s) => e.starts(*s),
162 None => e.remove_starts(),
163 };
164 }
165
166 if let Some(end) = &self.end {
167 match end {
168 Some(ed) => e.ends(*ed),
169 None => e.remove_ends(),
170 };
171 }
172
173 if let Some(status) = self.status {
174 e.status(status.into());
175 }
176
177 if let Some(summary) = &self.summary {
178 e.summary(summary);
179 }
180
181 e
182 }
183}
184
185#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
187#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
188pub enum EventStatus {
189 Tentative,
191
192 #[default]
194 Confirmed,
195
196 Cancelled,
198}
199
200const STATUS_TENTATIVE: &str = "TENTATIVE";
201const STATUS_CONFIRMED: &str = "CONFIRMED";
202const STATUS_CANCELLED: &str = "CANCELLED";
203
204impl AsRef<str> for EventStatus {
205 fn as_ref(&self) -> &str {
206 match self {
207 EventStatus::Tentative => STATUS_TENTATIVE,
208 EventStatus::Confirmed => STATUS_CONFIRMED,
209 EventStatus::Cancelled => STATUS_CANCELLED,
210 }
211 }
212}
213
214impl Display for EventStatus {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 write!(f, "{}", self.as_ref())
217 }
218}
219
220impl FromStr for EventStatus {
221 type Err = ();
222
223 fn from_str(value: &str) -> Result<Self, Self::Err> {
224 match value {
225 STATUS_TENTATIVE => Ok(EventStatus::Tentative),
226 STATUS_CONFIRMED => Ok(EventStatus::Confirmed),
227 STATUS_CANCELLED => Ok(EventStatus::Cancelled),
228 _ => Err(()),
229 }
230 }
231}
232
233impl From<EventStatus> for icalendar::EventStatus {
234 fn from(status: EventStatus) -> Self {
235 match status {
236 EventStatus::Tentative => icalendar::EventStatus::Tentative,
237 EventStatus::Confirmed => icalendar::EventStatus::Confirmed,
238 EventStatus::Cancelled => icalendar::EventStatus::Cancelled,
239 }
240 }
241}
242
243impl From<icalendar::EventStatus> for EventStatus {
244 fn from(status: icalendar::EventStatus) -> Self {
245 match status {
246 icalendar::EventStatus::Tentative => EventStatus::Tentative,
247 icalendar::EventStatus::Confirmed => EventStatus::Confirmed,
248 icalendar::EventStatus::Cancelled => EventStatus::Cancelled,
249 }
250 }
251}
252
253#[derive(Debug, Clone, Copy)]
255pub struct EventConditions {
256 pub startable: bool,
258}
259
260#[derive(Debug)]
261pub(crate) struct ParsedEventConditions {
262 pub end_after: Option<DateTime<Local>>,
264}
265
266impl ParsedEventConditions {
267 pub fn parse(now: &DateTime<Local>, conds: &EventConditions) -> Self {
268 Self {
269 end_after: conds.startable.then_some(*now),
270 }
271 }
272}