use std::collections::HashMap;
pub mod task;
pub mod note;
pub mod log_elem;
pub mod reminder;
pub mod mps_group;
pub mod character;
pub use task::TaskData;
pub use note::NoteData;
pub use log_elem::LogData;
pub use reminder::ReminderData;
pub use mps_group::MpsGroupData;
pub use character::CharacterData;
#[derive(Debug, Clone, Default)]
pub struct ParsedArgs {
pub attrs: HashMap<String, String>,
pub tags: Vec<String>,
}
pub fn split_args(raw: &str) -> ParsedArgs {
let mut attrs = HashMap::new();
let mut tags = Vec::new();
if raw.trim().is_empty() {
return ParsedArgs::default();
}
for part in raw.split(',') {
let part = part.trim();
if part.is_empty() { continue; }
if let Some(colon) = part.find(':') {
let key = part[..colon].trim().to_string();
let val = part[colon + 1..].trim().to_string();
attrs.insert(key, val);
} else {
tags.push(part.to_string());
}
}
ParsedArgs { attrs, tags }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ElementKind {
Task,
Note,
Log,
Reminder,
MpsGroup,
Character,
Unknown,
}
impl std::fmt::Display for ElementKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ElementKind::Task => write!(f, "task"),
ElementKind::Note => write!(f, "note"),
ElementKind::Log => write!(f, "log"),
ElementKind::Reminder => write!(f, "reminder"),
ElementKind::MpsGroup => write!(f, "mps"),
ElementKind::Character => write!(f, "character"),
ElementKind::Unknown => write!(f, "unknown"),
}
}
}
impl ElementKind {
pub fn from_sign(sign: &str) -> Self {
match sign {
"task" => ElementKind::Task,
"note" => ElementKind::Note,
"log" => ElementKind::Log,
"reminder" => ElementKind::Reminder,
"mps" => ElementKind::MpsGroup,
"character" => ElementKind::Character,
_ => ElementKind::Unknown,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum Element {
Task {
raw_args: String,
refs: Vec<u64>,
body_str: String,
data: TaskData,
},
Note {
raw_args: String,
refs: Vec<u64>,
body_str: String,
data: NoteData,
},
Log {
raw_args: String,
refs: Vec<u64>,
body_str: String,
data: LogData,
},
Reminder {
raw_args: String,
refs: Vec<u64>,
body_str: String,
data: ReminderData,
},
MpsGroup {
raw_args: String,
refs: Vec<u64>,
body_str: String,
data: MpsGroupData,
},
Character {
raw_args: String,
refs: Vec<u64>,
body_str: String,
data: CharacterData,
},
Unknown {
sign: String,
raw_args: String,
refs: Vec<u64>,
body_str: String,
},
}
impl Element {
pub fn kind(&self) -> ElementKind {
match self {
Element::Task { .. } => ElementKind::Task,
Element::Note { .. } => ElementKind::Note,
Element::Log { .. } => ElementKind::Log,
Element::Reminder { .. } => ElementKind::Reminder,
Element::MpsGroup { .. } => ElementKind::MpsGroup,
Element::Character { .. } => ElementKind::Character,
Element::Unknown { .. } => ElementKind::Unknown,
}
}
pub fn is_mps_group(&self) -> bool { matches!(self, Element::MpsGroup { .. }) }
pub fn tags(&self) -> &[String] {
match self {
Element::Task { data, .. } => &data.tags,
Element::Note { data, .. } => &data.tags,
Element::Log { data, .. } => &data.tags,
Element::Reminder { data, .. } => &data.tags,
Element::MpsGroup { data, .. } => &data.tags,
Element::Character { data, .. } => &data.tags,
Element::Unknown { .. } => &[],
}
}
pub fn body_str(&self) -> &str {
match self {
Element::Task { body_str, .. } => body_str,
Element::Note { body_str, .. } => body_str,
Element::Log { body_str, .. } => body_str,
Element::Reminder { body_str, .. } => body_str,
Element::MpsGroup { body_str, .. } => body_str,
Element::Character { body_str, .. } => body_str,
Element::Unknown { body_str, .. } => body_str,
}
}
#[allow(dead_code)]
pub fn refs(&self) -> &[u64] {
match self {
Element::Task { refs, .. } => refs,
Element::Note { refs, .. } => refs,
Element::Log { refs, .. } => refs,
Element::Reminder { refs, .. } => refs,
Element::MpsGroup { refs, .. } => refs,
Element::Character { refs, .. } => refs,
Element::Unknown { refs, .. } => refs,
}
}
pub fn sign(&self) -> &str {
match self {
Element::Task { .. } => "task",
Element::Note { .. } => "note",
Element::Log { .. } => "log",
Element::Reminder { .. } => "reminder",
Element::MpsGroup { .. } => "mps",
Element::Character { .. } => "character",
Element::Unknown { sign, .. } => sign,
}
}
pub fn raw_args(&self) -> &str {
match self {
Element::Task { raw_args, .. } => raw_args,
Element::Note { raw_args, .. } => raw_args,
Element::Log { raw_args, .. } => raw_args,
Element::Reminder { raw_args, .. } => raw_args,
Element::MpsGroup { raw_args, .. } => raw_args,
Element::Character { raw_args, .. } => raw_args,
Element::Unknown { raw_args, .. } => raw_args,
}
}
pub fn typed_attrs(&self) -> Vec<(String, String)> {
match self {
Element::Task { data, .. } => vec![
("status".into(), data.status_str().into()),
],
Element::Log { data, .. } => {
let mut attrs = Vec::new();
if let Some(ref s) = data.start { attrs.push(("start".into(), s.clone())); }
if let Some(ref e) = data.end { attrs.push(("end".into(), e.clone())); }
attrs
}
Element::Reminder { data, .. } => {
if let Some(ref a) = data.at {
vec![("at".into(), a.clone())]
} else {
Vec::new()
}
}
Element::Character { data, .. } => {
if let Some(ref n) = data.name {
vec![("name".into(), n.clone())]
} else {
Vec::new()
}
}
_ => Vec::new(),
}
}
pub fn is_unknown(&self) -> bool { matches!(self, Element::Unknown { .. }) }
pub fn from_parts(sign: &str, raw_args: String, refs: Vec<u64>, body_str: String) -> Self {
match sign {
"task" => Element::Task {
data: TaskData::parse_args(&raw_args),
raw_args, refs, body_str,
},
"note" => Element::Note {
data: NoteData::parse_args(&raw_args),
raw_args, refs, body_str,
},
"log" => Element::Log {
data: LogData::parse_args(&raw_args),
raw_args, refs, body_str,
},
"reminder" => Element::Reminder {
data: ReminderData::parse_args(&raw_args),
raw_args, refs, body_str,
},
"mps" => Element::MpsGroup {
data: MpsGroupData::parse_args(&raw_args),
raw_args, refs, body_str,
},
"character" => Element::Character {
data: CharacterData::parse_args(&raw_args),
raw_args, refs, body_str,
},
other => Element::Unknown {
sign: other.to_string(),
raw_args, refs, body_str,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_args_empty() {
let p = split_args("");
assert!(p.tags.is_empty());
assert!(p.attrs.is_empty());
}
#[test]
fn test_split_args_tags_only() {
let p = split_args("work, release");
assert_eq!(p.tags, vec!["work", "release"]);
assert!(p.attrs.is_empty());
}
#[test]
fn test_split_args_attrs_only() {
let p = split_args("status: done");
assert!(p.tags.is_empty());
assert_eq!(p.attrs.get("status").map(|s| s.as_str()), Some("done"));
}
#[test]
fn test_split_args_mixed() {
let p = split_args("work, release, status: done");
assert_eq!(p.tags, vec!["work", "release"]);
assert_eq!(p.attrs.get("status").map(|s| s.as_str()), Some("done"));
}
#[test]
fn test_split_args_at_field() {
let p = split_args("at: 5pm");
assert_eq!(p.attrs.get("at").map(|s| s.as_str()), Some("5pm"));
}
#[test]
fn test_element_kind_from_sign() {
assert_eq!(ElementKind::from_sign("task"), ElementKind::Task);
assert_eq!(ElementKind::from_sign("note"), ElementKind::Note);
assert_eq!(ElementKind::from_sign("log"), ElementKind::Log);
assert_eq!(ElementKind::from_sign("reminder"), ElementKind::Reminder);
assert_eq!(ElementKind::from_sign("mps"), ElementKind::MpsGroup);
assert_eq!(ElementKind::from_sign("character"), ElementKind::Character);
assert_eq!(ElementKind::from_sign("unknown"), ElementKind::Unknown);
}
}