1use std::collections::HashMap;
8
9pub mod task;
10pub mod note;
11pub mod log_elem;
12pub mod reminder;
13pub mod mps_group;
14
15pub use task::TaskData;
16pub use note::NoteData;
17pub use log_elem::LogData;
18pub use reminder::ReminderData;
19pub use mps_group::MpsGroupData;
20
21#[derive(Debug, Clone, Default)]
24pub struct ParsedArgs {
25 pub attrs: HashMap<String, String>,
26 pub tags: Vec<String>,
27}
28
29pub fn split_args(raw: &str) -> ParsedArgs {
32 let mut attrs = HashMap::new();
33 let mut tags = Vec::new();
34 if raw.trim().is_empty() {
35 return ParsedArgs::default();
36 }
37 for part in raw.split(',') {
38 let part = part.trim();
39 if part.is_empty() { continue; }
40 if let Some(colon) = part.find(':') {
41 let key = part[..colon].trim().to_string();
42 let val = part[colon + 1..].trim().to_string();
43 attrs.insert(key, val);
44 } else {
45 tags.push(part.to_string());
46 }
47 }
48 ParsedArgs { attrs, tags }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
53pub enum ElementKind {
54 Task,
55 Note,
56 Log,
57 Reminder,
58 MpsGroup,
59 Unknown,
60}
61
62impl std::fmt::Display for ElementKind {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 ElementKind::Task => write!(f, "task"),
66 ElementKind::Note => write!(f, "note"),
67 ElementKind::Log => write!(f, "log"),
68 ElementKind::Reminder => write!(f, "reminder"),
69 ElementKind::MpsGroup => write!(f, "mps"),
70 ElementKind::Unknown => write!(f, "unknown"),
71 }
72 }
73}
74
75impl ElementKind {
76 pub fn from_sign(sign: &str) -> Self {
77 match sign {
78 "task" => ElementKind::Task,
79 "note" => ElementKind::Note,
80 "log" => ElementKind::Log,
81 "reminder" => ElementKind::Reminder,
82 "mps" => ElementKind::MpsGroup,
83 _ => ElementKind::Unknown,
84 }
85 }
86}
87
88#[allow(dead_code)]
89#[derive(Debug, Clone)]
93pub enum Element {
94 Task {
95 raw_args: String,
96 refs: Vec<u64>,
97 body_str: String,
98 data: TaskData,
99 },
100 Note {
101 raw_args: String,
102 refs: Vec<u64>,
103 body_str: String,
104 data: NoteData,
105 },
106 Log {
107 raw_args: String,
108 refs: Vec<u64>,
109 body_str: String,
110 data: LogData,
111 },
112 Reminder {
113 raw_args: String,
114 refs: Vec<u64>,
115 body_str: String,
116 data: ReminderData,
117 },
118 MpsGroup {
119 raw_args: String,
120 refs: Vec<u64>,
121 body_str: String,
122 data: MpsGroupData,
123 },
124 Unknown {
125 sign: String,
126 raw_args: String,
127 refs: Vec<u64>,
128 body_str: String,
129 },
130}
131
132impl Element {
133 pub fn kind(&self) -> ElementKind {
134 match self {
135 Element::Task { .. } => ElementKind::Task,
136 Element::Note { .. } => ElementKind::Note,
137 Element::Log { .. } => ElementKind::Log,
138 Element::Reminder { .. } => ElementKind::Reminder,
139 Element::MpsGroup { .. } => ElementKind::MpsGroup,
140 Element::Unknown { .. } => ElementKind::Unknown,
141 }
142 }
143
144 pub fn is_mps_group(&self) -> bool { matches!(self, Element::MpsGroup { .. }) }
145
146 pub fn tags(&self) -> &[String] {
147 match self {
148 Element::Task { data, .. } => &data.tags,
149 Element::Note { data, .. } => &data.tags,
150 Element::Log { data, .. } => &data.tags,
151 Element::Reminder { data, .. } => &data.tags,
152 Element::MpsGroup { data, .. } => &data.tags,
153 Element::Unknown { .. } => &[],
154 }
155 }
156
157 pub fn body_str(&self) -> &str {
158 match self {
159 Element::Task { body_str, .. } => body_str,
160 Element::Note { body_str, .. } => body_str,
161 Element::Log { body_str, .. } => body_str,
162 Element::Reminder { body_str, .. } => body_str,
163 Element::MpsGroup { body_str, .. } => body_str,
164 Element::Unknown { body_str, .. } => body_str,
165 }
166 }
167
168 #[allow(dead_code)]
169 pub fn refs(&self) -> &[u64] {
170 match self {
171 Element::Task { refs, .. } => refs,
172 Element::Note { refs, .. } => refs,
173 Element::Log { refs, .. } => refs,
174 Element::Reminder { refs, .. } => refs,
175 Element::MpsGroup { refs, .. } => refs,
176 Element::Unknown { refs, .. } => refs,
177 }
178 }
179
180 pub fn sign(&self) -> &str {
181 match self {
182 Element::Task { .. } => "task",
183 Element::Note { .. } => "note",
184 Element::Log { .. } => "log",
185 Element::Reminder { .. } => "reminder",
186 Element::MpsGroup { .. } => "mps",
187 Element::Unknown { sign, .. } => sign,
188 }
189 }
190
191 pub fn raw_args(&self) -> &str {
193 match self {
194 Element::Task { raw_args, .. } => raw_args,
195 Element::Note { raw_args, .. } => raw_args,
196 Element::Log { raw_args, .. } => raw_args,
197 Element::Reminder { raw_args, .. } => raw_args,
198 Element::MpsGroup { raw_args, .. } => raw_args,
199 Element::Unknown { raw_args, .. } => raw_args,
200 }
201 }
202
203 pub fn typed_attrs(&self) -> Vec<(String, String)> {
206 match self {
207 Element::Task { data, .. } => vec![
209 ("status".into(), data.status_str().into()),
210 ],
211 Element::Log { data, .. } => {
212 let mut attrs = Vec::new();
213 if let Some(ref s) = data.start { attrs.push(("start".into(), s.clone())); }
214 if let Some(ref e) = data.end { attrs.push(("end".into(), e.clone())); }
215 attrs
216 }
217 Element::Reminder { data, .. } => {
218 if let Some(ref a) = data.at {
219 vec![("at".into(), a.clone())]
220 } else {
221 Vec::new()
222 }
223 }
224 _ => Vec::new(),
225 }
226 }
227
228 pub fn is_unknown(&self) -> bool { matches!(self, Element::Unknown { .. }) }
230
231 pub fn from_parts(sign: &str, raw_args: String, refs: Vec<u64>, body_str: String) -> Self {
233 match sign {
234 "task" => Element::Task {
235 data: TaskData::parse_args(&raw_args),
236 raw_args, refs, body_str,
237 },
238 "note" => Element::Note {
239 data: NoteData::parse_args(&raw_args),
240 raw_args, refs, body_str,
241 },
242 "log" => Element::Log {
243 data: LogData::parse_args(&raw_args),
244 raw_args, refs, body_str,
245 },
246 "reminder" => Element::Reminder {
247 data: ReminderData::parse_args(&raw_args),
248 raw_args, refs, body_str,
249 },
250 "mps" => Element::MpsGroup {
251 data: MpsGroupData::parse_args(&raw_args),
252 raw_args, refs, body_str,
253 },
254 other => Element::Unknown {
255 sign: other.to_string(),
256 raw_args, refs, body_str,
257 },
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_split_args_empty() {
268 let p = split_args("");
269 assert!(p.tags.is_empty());
270 assert!(p.attrs.is_empty());
271 }
272
273 #[test]
274 fn test_split_args_tags_only() {
275 let p = split_args("work, release");
276 assert_eq!(p.tags, vec!["work", "release"]);
277 assert!(p.attrs.is_empty());
278 }
279
280 #[test]
281 fn test_split_args_attrs_only() {
282 let p = split_args("status: done");
283 assert!(p.tags.is_empty());
284 assert_eq!(p.attrs.get("status").map(|s| s.as_str()), Some("done"));
285 }
286
287 #[test]
288 fn test_split_args_mixed() {
289 let p = split_args("work, release, status: done");
290 assert_eq!(p.tags, vec!["work", "release"]);
291 assert_eq!(p.attrs.get("status").map(|s| s.as_str()), Some("done"));
292 }
293
294 #[test]
295 fn test_split_args_at_field() {
296 let p = split_args("at: 5pm");
297 assert_eq!(p.attrs.get("at").map(|s| s.as_str()), Some("5pm"));
298 }
299
300 #[test]
301 fn test_element_kind_from_sign() {
302 assert_eq!(ElementKind::from_sign("task"), ElementKind::Task);
303 assert_eq!(ElementKind::from_sign("note"), ElementKind::Note);
304 assert_eq!(ElementKind::from_sign("log"), ElementKind::Log);
305 assert_eq!(ElementKind::from_sign("reminder"), ElementKind::Reminder);
306 assert_eq!(ElementKind::from_sign("mps"), ElementKind::MpsGroup);
307 assert_eq!(ElementKind::from_sign("unknown"), ElementKind::Unknown);
308 }
309}