use std::collections::HashMap;
#[derive(Debug)]
pub struct ParsedTask {
pub title: String,
pub metadata: HashMap<String, String>,
pub content: String,
}
pub fn parse_task_md(content: &str) -> Result<ParsedTask, String> {
let lines: Vec<&str> = content.lines().collect();
if lines.is_empty() {
return Err("Empty file".to_string());
}
let title = lines[0]
.trim_start_matches('#')
.trim()
.to_string();
if title.is_empty() {
return Err("No title found".to_string());
}
let mut metadata = HashMap::new();
let mut content_lines: Vec<&str> = Vec::new();
let mut in_content = false;
for line in &lines[1..] {
let line = line.trim();
if line.is_empty() && !in_content {
continue;
}
if !in_content {
if let Some(colon_pos) = line.find(':') {
let key = line[..colon_pos].trim().to_string();
let value = line[colon_pos + 1..].trim().to_string();
if !key.is_empty() {
metadata.insert(key, value);
continue;
}
}
in_content = true;
}
if in_content {
content_lines.push(line);
}
}
Ok(ParsedTask {
title,
metadata,
content: content_lines.join("\n").trim().to_string(),
})
}
pub fn generate_task_md(
title: &str,
metadata: &HashMap<String, String>,
content: &str,
) -> String {
let mut output = format!("# {}\n\n", title);
for (key, value) in metadata {
output.push_str(&format!("{}: {}\n", key, value));
}
if !content.is_empty() {
output.push_str("\n");
output.push_str(content);
output.push('\n');
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_task() {
let md = r#"# Test Task
created: 2025-12-08
priority: high
This is the task content.
It can span multiple lines.
"#;
let parsed = parse_task_md(md).unwrap();
assert_eq!(parsed.title, "Test Task");
assert_eq!(parsed.metadata.get("created"), Some(&"2025-12-08".to_string()));
assert_eq!(parsed.metadata.get("priority"), Some(&"high".to_string()));
assert!(parsed.content.contains("task content"));
}
#[test]
fn test_generate_task_md() {
let mut metadata = HashMap::new();
metadata.insert("created".to_string(), "2025-12-08".to_string());
let md = generate_task_md("Test", &metadata, "Content");
assert!(md.contains("# Test"));
assert!(md.contains("created: 2025-12-08"));
assert!(md.contains("Content"));
}
}