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