1use crate::{DatePerhapsTime, Priority, SortOrder};
6use chrono::{DateTime, Duration, FixedOffset, NaiveDateTime, Utc};
7use icalendar::Component;
8use std::{fmt::Display, str::FromStr};
9
10const KEY_COMPLETED: &str = "COMPLETED";
11const KEY_DESCRIPTION: &str = "DESCRIPTION";
12const KEY_DUE: &str = "DUE";
13
14pub trait Todo {
16 fn uid(&self) -> &str;
18 fn completed(&self) -> Option<DateTime<FixedOffset>>;
20 fn description(&self) -> Option<&str>;
22 fn due(&self) -> Option<DatePerhapsTime>;
24 fn percent(&self) -> Option<u8>;
26 fn priority(&self) -> Priority;
28 fn status(&self) -> Option<TodoStatus>;
30 fn summary(&self) -> &str;
32}
33
34#[derive(Debug, Default, Clone)]
36pub struct TodoPatch {
37 pub uid: String,
39
40 pub completed: Option<Option<DateTime<FixedOffset>>>,
42
43 pub description: Option<Option<String>>,
45
46 pub due: Option<Option<DatePerhapsTime>>,
48
49 pub percent: Option<Option<u8>>,
51
52 pub priority: Option<Priority>,
54
55 pub status: Option<TodoStatus>,
57
58 pub summary: Option<String>,
60}
61
62impl TodoPatch {
63 pub fn is_empty(&self) -> bool {
65 self.completed.is_none()
66 && self.description.is_none()
67 && self.due.is_none()
68 && self.percent.is_none()
69 && self.priority.is_none()
70 && self.status.is_none()
71 && self.summary.is_none()
72 }
73
74 pub fn apply_to<'a>(&self, t: &'a mut icalendar::Todo) -> &'a mut icalendar::Todo {
76 if let Some(completed) = self.completed {
77 match completed {
78 Some(dt) => t.completed(dt.with_timezone(&Utc)),
79 None => t.remove_property(KEY_COMPLETED),
80 };
81 }
82
83 if let Some(description) = &self.description {
84 match description {
85 Some(desc) => t.description(desc),
86 None => t.remove_property(KEY_DESCRIPTION),
87 };
88 }
89
90 if let Some(due) = &self.due {
91 match due {
92 Some(d) => t.due(*d),
93 None => t.remove_property(KEY_DUE),
94 };
95 }
96
97 if let Some(percent) = self.percent {
98 t.percent_complete(percent.unwrap_or(0));
99 }
100
101 if let Some(priority) = self.priority {
102 t.priority(priority.into());
103 }
104
105 if let Some(status) = self.status {
106 t.status(status.into());
107 }
108
109 if let Some(summary) = &self.summary {
110 t.summary(summary);
111 }
112
113 t
114 }
115}
116
117#[derive(Debug, Clone, Copy)]
119pub enum TodoStatus {
120 NeedsAction,
122 Completed,
124 InProcess,
126 Cancelled,
128}
129
130const STATUS_NEEDS_ACTION: &str = "NEEDS-ACTION";
131const STATUS_COMPLETED: &str = "COMPLETED";
132const STATUS_IN_PROCESS: &str = "IN-PROGRESS";
133const STATUS_CANCELLED: &str = "CANCELLED";
134
135impl AsRef<str> for TodoStatus {
136 fn as_ref(&self) -> &str {
137 match self {
138 TodoStatus::NeedsAction => STATUS_NEEDS_ACTION,
139 TodoStatus::Completed => STATUS_COMPLETED,
140 TodoStatus::InProcess => STATUS_IN_PROCESS,
141 TodoStatus::Cancelled => STATUS_CANCELLED,
142 }
143 }
144}
145
146impl Display for TodoStatus {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "{}", self.as_ref())
149 }
150}
151
152impl FromStr for TodoStatus {
153 type Err = ();
154
155 fn from_str(value: &str) -> Result<Self, Self::Err> {
156 match value {
157 STATUS_NEEDS_ACTION => Ok(TodoStatus::NeedsAction),
158 STATUS_COMPLETED => Ok(TodoStatus::Completed),
159 STATUS_IN_PROCESS => Ok(TodoStatus::InProcess),
160 STATUS_CANCELLED => Ok(TodoStatus::Cancelled),
161 _ => Err(()),
162 }
163 }
164}
165
166impl From<TodoStatus> for icalendar::TodoStatus {
167 fn from(item: TodoStatus) -> icalendar::TodoStatus {
168 match item {
169 TodoStatus::NeedsAction => icalendar::TodoStatus::NeedsAction,
170 TodoStatus::Completed => icalendar::TodoStatus::Completed,
171 TodoStatus::InProcess => icalendar::TodoStatus::InProcess,
172 TodoStatus::Cancelled => icalendar::TodoStatus::Cancelled,
173 }
174 }
175}
176
177impl From<&icalendar::TodoStatus> for TodoStatus {
178 fn from(status: &icalendar::TodoStatus) -> Self {
179 match status {
180 icalendar::TodoStatus::NeedsAction => TodoStatus::NeedsAction,
181 icalendar::TodoStatus::Completed => TodoStatus::Completed,
182 icalendar::TodoStatus::InProcess => TodoStatus::InProcess,
183 icalendar::TodoStatus::Cancelled => TodoStatus::Cancelled,
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy)]
190pub struct TodoConditions {
191 pub now: NaiveDateTime,
193
194 pub status: Option<TodoStatus>,
196
197 pub due: Option<Duration>,
199}
200
201impl TodoConditions {
202 pub fn due_before(&self) -> Option<NaiveDateTime> {
204 self.due.map(|a| self.now + a)
205 }
206}
207
208#[derive(Debug, Clone, Copy)]
210pub enum TodoSort {
211 Due(SortOrder),
213
214 Priority {
216 order: SortOrder,
218
219 none_first: bool,
221 },
222}