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