From cd97c18fef5063b5a32e0a11a2b2129637089685 Mon Sep 17 00:00:00 2001 From: Andrei Stoica Date: Sun, 25 Jun 2023 12:25:59 -0400 Subject: [PATCH] subtasks --- src/main.rs | 4 +- src/todo/tasks.rs | 105 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 95 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index f224694..12196fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,8 +61,8 @@ fn main() { let arena = Arena::new(); let root = parse_todo_file(&file, &arena); - //println!("{:#?}", root); - //println!("======================================================="); + println!("{:#?}", root); + println!("======================================================="); let sections = &cfg.sections.unwrap(); let groups = extract_secitons(root, sections); diff --git a/src/todo/tasks.rs b/src/todo/tasks.rs index 82edf21..11d580b 100644 --- a/src/todo/tasks.rs +++ b/src/todo/tasks.rs @@ -15,6 +15,8 @@ pub struct TaskGroup { pub struct Task { pub status: Status, pub text: String, + level: u8, + pub subtasks: Option>, } #[derive(Debug, PartialEq, Clone)] @@ -24,7 +26,7 @@ pub enum Status { Empty, } -pub enum TaskErorr { +pub enum TaskError { ParsingError(&'static str), } @@ -39,6 +41,42 @@ impl Task { } text } + + fn extract_text<'a>(node: &'a AstNode<'a>) -> Result { + let data_ref = node.data.borrow(); + if let NodeValue::Text(contents) = &data_ref.value { + Ok(contents.to_string()) + } else { + Err(TaskError::ParsingError("Could not get text from element")) + } + } + + fn extract_text_from_task<'a>(node: &'a AstNode<'a>) -> Result { + let mut text = String::new(); + let data_ref = node.data.borrow(); + if let NodeValue::Paragraph = data_ref.value { + for child in node.children() { + let child_data_ref = child.data.borrow(); + let t = match &child_data_ref.borrow().value { + NodeValue::Text(contents) => contents.clone(), + NodeValue::Emph if child.first_child().is_some() => { + format!("*{}*", Self::extract_text(child.first_child().unwrap())?) + } + NodeValue::Strong if child.first_child().is_some() => { + format!("**{}**", Self::extract_text(child.first_child().unwrap())?) + } + NodeValue::SoftBreak => { + format!("\n{}", " ".repeat(data_ref.sourcepos.start.column)) + } + _ => "".into(), + }; + text.push_str(&t); + } + Ok(text) + } else { + Err(TaskError::ParsingError("First child is not Paragraph")) + } + } } impl ToString for Task { @@ -48,25 +86,68 @@ impl ToString for Task { Status::Todo(ch) => ch, Status::Empty => ' ', }; - format!(" - [{}] {}\n", ch, self.text.trim()) + + let subtasks = if let Some(subtasks) = &self.subtasks { + let mut text = subtasks + .iter() + .map(|task| task.to_string()) + .collect::>() + .join("\n"); + text.insert(0, '\n'); + text.trim_end().to_string() + } else { + "".into() + }; + + format!( + " - [{}] {}{}\n", + ch, + self.text.trim(), + subtasks.replace("\n", "\n ") + ) } } impl<'a> TryFrom<&'a AstNode<'a>> for Task { - type Error = TaskErorr; + type Error = TaskError; fn try_from(node: &'a AstNode<'a>) -> Result { let data_ref = &node.data.borrow(); if let NodeValue::TaskItem(ch) = data_ref.value { - let text = Self::find_text(node); + let text = Self::extract_text_from_task( + node.first_child() + .ok_or(TaskError::ParsingError("No childern of node found"))?, + )?; let status = match ch { Some(c) if c == 'x' || c == 'X' => Status::Done(c), Some(c) => Status::Todo(c), _ => Status::Empty, }; + let subtasks = node + .children() + .filter_map(|child| { + if let NodeValue::List(_) = child.data.borrow().value { + Some(child) + } else { + None + } + }) + .map(|child| { + child + .children() + .into_iter() + .filter_map(|item_node| Task::try_from(item_node).ok()) + .collect() + }) + .reduce(|a: Vec, b: Vec| [a, b].concat()); - Ok(Self { status, text }) + Ok(Self { + status, + text, + subtasks, + level: 1, + }) } else { - Err(TaskErorr::ParsingError( + Err(TaskError::ParsingError( "Node being parsed is not a TaskItem", )) } @@ -95,7 +176,7 @@ impl ToString for TaskGroup { } impl<'a> TryFrom<&'a AstNode<'a>> for TaskGroup { - type Error = TaskErorr; + type Error = TaskError; fn try_from(node: &'a AstNode<'a>) -> Result { let node_ref = &node.data.borrow(); if let NodeValue::Heading(heading) = node_ref.value { @@ -104,21 +185,21 @@ impl<'a> TryFrom<&'a AstNode<'a>> for TaskGroup { let first_child = if let Some(child) = first_child_ref.borrow() { child } else { - return Err(TaskErorr::ParsingError("Node has no children")); + return Err(TaskError::ParsingError("Node has no children")); }; let data_ref = &first_child.data.borrow(); let name = if let NodeValue::Text(value) = &data_ref.value { value.to_string() } else { - return Err(TaskErorr::ParsingError( + return Err(TaskError::ParsingError( "Could not get title from heading node", )); }; let next_sib = node .next_sibling() - .ok_or(TaskErorr::ParsingError("Empty section at end of file"))?; + .ok_or(TaskError::ParsingError("Empty section at end of file"))?; if let NodeValue::List(_list_meta) = next_sib.data.borrow().value { let tasks = next_sib @@ -129,12 +210,12 @@ impl<'a> TryFrom<&'a AstNode<'a>> for TaskGroup { Ok(TaskGroup { name, tasks, level }) } else { - Err(TaskErorr::ParsingError( + Err(TaskError::ParsingError( "Next sibling of node is not a list", )) } } else { - Err(TaskErorr::ParsingError("Node is not a section heading")) + Err(TaskError::ParsingError("Node is not a section heading")) } } }