diff --git a/src/config/mod.rs b/src/config/mod.rs index a475009..2b8b5dc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -21,7 +21,7 @@ impl Default for Config { Config { editor: "nano".into(), sections: vec!["Daily".into(), "Weekly".into(), "Monthly".into()], - notes_dir: "./Notes".into(), + notes_dir: "~/Notes".into(), } } } diff --git a/src/file/mod.rs b/src/file/mod.rs new file mode 100644 index 0000000..63f43b6 --- /dev/null +++ b/src/file/mod.rs @@ -0,0 +1,116 @@ +use chrono::Datelike; +use crate::TaskGroup; +use crate::NaiveDate; +use crate::todo::{Status as TaskStatus,File as TodoFile}; +use comrak::nodes::{AstNode, NodeValue}; +use comrak::{Arena, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions}; +use std::str; +use std::io::Write; +use std::collections::HashMap; +use comrak::parse_document; +use std::path::{Path, PathBuf}; +use std::fs::{read, read_dir, File}; + +pub fn get_filepath(data_dir: &PathBuf, date: &NaiveDate) -> PathBuf { + let file_name = format!("{}-{:02}-{:02}.md", date.year(), date.month(), date.day()); + let mut file_path = data_dir.clone(); + file_path.push(file_name); + + file_path +} + +pub fn generate_file_content(data: &Vec, date: &NaiveDate) -> String { + let mut content = format!( + "# Today's tasks {}-{:02}-{:02}\n", + date.year(), + date.month(), + date.day() + ); + data.iter() + .for_each(|task_group| content.push_str(format!("\n{}", task_group.to_string()).as_str())); + + content +} + +pub fn write_file(path: &PathBuf, content: &String) { + let mut new_file = File::create(&path).expect("Could not open today's file: {today_file_path}"); + write!(new_file, "{}", content).expect("Could not write to file: {today_file_path}"); +} + +pub fn load_file(file: &TodoFile) -> String { + let contents_utf8 = read(file.file.path()) + .expect(format!("Could not read file {}", file.file.path().to_string_lossy()).as_str()); + str::from_utf8(&contents_utf8) + .expect( + format!( + "failed to convert contents of file to string: {}", + file.file.path().to_string_lossy() + ) + .as_str(), + ) + .to_string() +} + +pub fn parse_todo_file<'a>(contents: &String, arena: &'a Arena>) -> &'a AstNode<'a> { + let options = &ComrakOptions { + extension: ComrakExtensionOptions { + tasklist: true, + ..ComrakExtensionOptions::default() + }, + parse: ComrakParseOptions { + relaxed_tasklist_matching: true, + ..ComrakParseOptions::default() + }, + ..ComrakOptions::default() + }; + parse_document(arena, contents, options) +} + +pub fn extract_secitons<'a>( + root: &'a AstNode<'a>, + sections: &Vec, +) -> HashMap { + let mut groups: HashMap = HashMap::new(); + for node in root.reverse_children() { + let node_ref = &node.data.borrow(); + if let NodeValue::Heading(heading) = node_ref.value { + if heading.level < 2 { + continue; + } + + let first_child_ref = &node.first_child(); + let first_child = if let Some(child) = first_child_ref { + child + } else { + continue; + }; + + let data_ref = &first_child.data.borrow(); + let title = if let NodeValue::Text(value) = &data_ref.value { + value + } else { + continue; + }; + + if sections.iter().any(|section| section.eq(title)) { + if let Ok(mut group) = TaskGroup::try_from(node) { + group.tasks = group + .tasks + .into_iter() + .filter(|task| !matches!(task.status, TaskStatus::Done(_))) + .collect(); + groups.insert(title.to_string(), group); + } + } + }; + } + groups +} + +pub fn get_latest_file(dir: &Path) -> Result { + let dir = read_dir(dir).expect(format!("Could not find notes folder: {:?}", dir).as_str()); + dir.filter_map(|f| f.ok()) + .filter_map(|file| TodoFile::try_from(file).ok()) + .reduce(|a, b| TodoFile::latest_file(a, b)) + .ok_or("Could not reduce items".to_string()) +} diff --git a/src/main.rs b/src/main.rs index a2fd7dc..11f8c7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,19 @@ mod cli; mod config; +mod file; mod todo; use crate::cli::Args; -use clap::Parser; - use crate::config::Config; -use crate::todo::File as TodoFile; -use crate::todo::{Status as TaskStatus, TaskGroup}; +use crate::todo::TaskGroup; use chrono::naive::NaiveDate; use chrono::{Datelike, Local}; -use comrak::nodes::{AstNode, NodeValue}; -use comrak::{parse_document, Arena}; -use comrak::{ComrakExtensionOptions, ComrakOptions, ComrakParseOptions}; +use clap::Parser; +use comrak::Arena; use resolve_path::PathResolveExt; -use std::collections::HashMap; -use std::fs::{create_dir_all, metadata, read, read_dir, File}; -use std::io::Write; -use std::path::{Path, PathBuf}; +use std::fs; +use std::path::Path; use std::process::Command; -use std::str; //TODO refactor creating new file @@ -66,14 +60,14 @@ fn main() { let data_dir = cfg.notes_dir.resolve().to_path_buf(); - if !metadata(&data_dir).is_ok() { - match create_dir_all(&data_dir) { + if !fs::metadata(&data_dir).is_ok() { + match fs::create_dir_all(&data_dir) { Err(_e) => panic!("Could not create defult directory: {:?}", &data_dir), _ => (), }; } - let latest_file = get_latest_file(&data_dir); + let latest_file = file::get_latest_file(&data_dir); let now = Local::now(); let today = NaiveDate::from_ymd_opt(now.year(), now.month(), now.day()).unwrap(); @@ -81,14 +75,14 @@ fn main() { Ok(todo_file) if todo_file.date < today => { let arena = Arena::new(); let root = { - let contents = load_file(&todo_file); - let root = parse_todo_file(&contents, &arena); + let contents = file::load_file(&todo_file); + let root = file::parse_todo_file(&contents, &arena); root }; let sections = &cfg.sections; - let groups = extract_secitons(root, sections); + let groups = file::extract_secitons(root, sections); let level = groups.values().map(|group| group.level).min().unwrap_or(2); @@ -100,9 +94,9 @@ fn main() { }) .collect(); - let content = generate_file_content(&data, &today); - let file_path = get_filepath(&data_dir, &today); - write_file(&file_path, &content); + let content = file::generate_file_content(&data, &today); + let file_path = file::get_filepath(&data_dir, &today); + file::write_file(&file_path, &content); file_path } Err(_) => { @@ -111,9 +105,9 @@ fn main() { .iter() .map(|sec| TaskGroup::empty(sec.clone(), 2)) .collect(); - let content = generate_file_content(&data, &today); - let file_path = get_filepath(&data_dir, &today); - write_file(&file_path, &content); + let content = file::generate_file_content(&data, &today); + let file_path = file::get_filepath(&data_dir, &today); + file::write_file(&file_path, &content); file_path } Ok(todo_file) => todo_file.file.path(), @@ -124,107 +118,3 @@ fn main() { .status() .expect(format!("failed to launch editor {}", "vim").as_str()); } - -fn get_filepath(data_dir: &PathBuf, date: &NaiveDate) -> PathBuf { - let file_name = format!("{}-{:02}-{:02}.md", date.year(), date.month(), date.day()); - let mut file_path = data_dir.clone(); - file_path.push(file_name); - - file_path -} - -fn generate_file_content(data: &Vec, date: &NaiveDate) -> String { - let mut content = format!( - "# Today's tasks {}-{:02}-{:02}\n", - date.year(), - date.month(), - date.day() - ); - data.iter() - .for_each(|task_group| content.push_str(format!("\n{}", task_group.to_string()).as_str())); - - content -} - -fn write_file(path: &PathBuf, content: &String) { - let mut new_file = File::create(&path).expect("Could not open today's file: {today_file_path}"); - write!(new_file, "{}", content).expect("Could not write to file: {today_file_path}"); -} - -fn load_file(file: &TodoFile) -> String { - let contents_utf8 = read(file.file.path()) - .expect(format!("Could not read file {}", file.file.path().to_string_lossy()).as_str()); - str::from_utf8(&contents_utf8) - .expect( - format!( - "failed to convert contents of file to string: {}", - file.file.path().to_string_lossy() - ) - .as_str(), - ) - .to_string() -} - -fn parse_todo_file<'a>(contents: &String, arena: &'a Arena>) -> &'a AstNode<'a> { - let options = &ComrakOptions { - extension: ComrakExtensionOptions { - tasklist: true, - ..ComrakExtensionOptions::default() - }, - parse: ComrakParseOptions { - relaxed_tasklist_matching: true, - ..ComrakParseOptions::default() - }, - ..ComrakOptions::default() - }; - parse_document(arena, contents, options) -} - -fn extract_secitons<'a>( - root: &'a AstNode<'a>, - sections: &Vec, -) -> HashMap { - let mut groups: HashMap = HashMap::new(); - for node in root.reverse_children() { - let node_ref = &node.data.borrow(); - if let NodeValue::Heading(heading) = node_ref.value { - if heading.level < 2 { - continue; - } - - let first_child_ref = &node.first_child(); - let first_child = if let Some(child) = first_child_ref { - child - } else { - continue; - }; - - let data_ref = &first_child.data.borrow(); - let title = if let NodeValue::Text(value) = &data_ref.value { - value - } else { - continue; - }; - - if sections.iter().any(|section| section.eq(title)) { - if let Ok(mut group) = TaskGroup::try_from(node) { - group.tasks = group - .tasks - .into_iter() - .filter(|task| !matches!(task.status, TaskStatus::Done(_))) - .collect(); - groups.insert(title.to_string(), group); - } - } - }; - } - groups -} - -fn get_latest_file(dir: &Path) -> Result { - let dir = read_dir(dir).expect(format!("Could not find notes folder: {:?}", dir).as_str()); - dir.filter_map(|f| f.ok()) - .filter_map(|file| TodoFile::try_from(file).ok()) - .reduce(|a, b| TodoFile::latest_file(a, b)) - .ok_or("Could not reduce items".to_string()) -} diff --git a/src/todo/mod.rs b/src/todo/mod.rs index fcd1155..7534deb 100644 --- a/src/todo/mod.rs +++ b/src/todo/mod.rs @@ -2,4 +2,4 @@ mod file; mod tasks; pub use file::File; -pub use tasks::{Status, Task, TaskGroup}; +pub use tasks::{Status, TaskGroup};