diff --git a/Cargo.lock b/Cargo.lock index 72fcd46..0a761c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,20 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "agenda" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "colored", + "dirs", + "fuzzydate", + "prettytable-rs", + "serde", + "serde_json", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -265,6 +279,17 @@ dependencies = [ "libc", ] +[[package]] +name = "fuzzydate" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cc638ce39f54e307c2648a5cf89fb549a0b66bc59f9de933b51b1dfe53c813" +dependencies = [ + "chrono", + "lazy_static", + "thiserror", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -587,19 +612,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tasks" -version = "0.1.0" -dependencies = [ - "chrono", - "clap", - "colored", - "dirs", - "prettytable-rs", - "serde", - "serde_json", -] - [[package]] name = "term" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index babfaf3..d9b4463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ clap = { version = "4.1.1", features = ["derive", "cargo"] } chrono = { version = "0.4.23", features = ["serde"] } dirs = "4.0.0" colored = "2.0.0" -prettytable-rs = "0.10.0" \ No newline at end of file +prettytable-rs = "0.10.0" +fuzzydate = "0.2.1" diff --git a/src/cli.rs b/src/cli.rs index 01c0505..cd09684 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,19 +1,21 @@ +use chrono::NaiveDateTime; use crate::args::{TasksArgs, Commands}; use crate::args::{CreateTask, DeleteTask, ShowTask, StartTask, StopTask, CompleteTask}; use crate::tasks::{Tasks, Task, Status}; use prettytable::{Table, Row, row, format}; use colored::*; +use fuzzydate; -fn success(msg: String) { +pub fn success(msg: String) { println!("{} {}", "success:".green().bold(), msg); } -fn warning(msg: &str) { +pub fn warning(msg: &str) { println!("{} {}", "warning:".yellow().bold(), msg); } #[allow(dead_code)] -fn error(msg: String) { +pub fn error(msg: String) { println!("{} {}", "error:".red().bold(), msg); panic!(); } @@ -29,6 +31,14 @@ fn get_task(tasks: &mut Tasks, id: usize) -> Task { } } +fn parse_date(date_string: Option) -> Option { + if date_string.is_some() { + Some(fuzzydate::parse(date_string.unwrap()).unwrap()) + } else { + None + } +} + fn calc_row(task: &Task, id: usize) -> Row { if task.status == Status::Complete { // Generate greyed out rows for complete tasks @@ -36,30 +46,73 @@ fn calc_row(task: &Task, id: usize) -> Row { task.status.as_string().bright_black().italic(), task.title.clone().bright_black().italic()]) } else { + let when = if task.when.is_some() { + format!("{}", task.when.unwrap().format("%Y-%m-%d")).bright_black() + } else { + String::from("N/A").bright_black() + }; + // Generate normal colored rows for uncompleted tasks - Row::from([id.to_string().cyan(), task.status.as_string(), task.title.clone().white()]) + Row::from([id.to_string().cyan(), task.status.as_string(), when, task.title.clone().white()]) } } pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> &mut Tasks { match arguments.command { - Commands::Add(CreateTask { title, .. }) => { - let task = Task::new(title); + Commands::Add(CreateTask { title, notes, tags, when, deadline, reminder, ..}) => { + let when = parse_date(when); + let deadline = parse_date(deadline); + let reminder = parse_date(reminder); + let tags: Option> = if tags.is_some() { + Some(tags.unwrap().split(",").map(str::to_string).collect()) + } else { + None + }; + + let task = Task::new(title, notes, tags, when, deadline, reminder); tasks.add(task.clone()); let id = tasks.len() - 1; success(task_msg("created", &task, id)); } + Commands::Del(DeleteTask { id }) => { - let mut binding = tasks.clone(); - let task = match binding.get_task(id) { - Ok(task) => task, - Err(error) => panic!("error: {}", error), - }; + let task = get_task(tasks, id); tasks.del(id); - success(task_msg("deleted", task, id)); + success(task_msg("deleted", &task, id)); } + + Commands::Done(CompleteTask { id }) => { + let task = get_task(&mut tasks.clone(), id); + + tasks.set_status(id, Status::Complete); + success(task_msg("completed", &task, id)); + } + + Commands::Start(StartTask { id }) => { + let task = get_task(&mut tasks.clone(), id); + + tasks.set_status(id, Status::Active); + success(task_msg("started", &task, id)); + } + + Commands::Stop(StopTask { id }) => { + let task = get_task(&mut tasks.clone(), id); + + if task.when.is_none() { + tasks.set_status(id, Status::Inbox); + } else { + tasks.set_status(id, Status::Pending); + }; + success(task_msg("stopped", &task, id)); + } + + Commands::Clear => { + tasks.clear(); + success(String::from("cleared all tasks")); + } + Commands::Show(ShowTask { id }) => { if id.is_none() { if tasks.tasks.is_none() { @@ -67,7 +120,7 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> &mut Tasks { } else { // Create the table for printing let mut table = Table::new(); - table.set_titles(row!["ID".magenta().bold(), "Status".magenta().bold(), "Title".magenta().bold()]); + table.set_titles(row!["ID".magenta().bold(), "Status".magenta().bold(), "When".magenta().bold(), "Title".magenta().bold()]); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); // Iterate through each task @@ -86,38 +139,13 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> &mut Tasks { // Generate and print the table let mut table = Table::new(); - table.set_titles(row!["ID".magenta().bold(), "Status".magenta().bold(), "Title".magenta().bold()]); + table.set_titles(row!["ID".magenta().bold(), "Status".magenta().bold(), "When".magenta().bold(), "Title".magenta().bold()]); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.add_row(calc_row(&task, id)); println!("{}", table) }; } - Commands::Done(CompleteTask { id }) => { - let task = get_task(&mut tasks.clone(), id); - tasks.set_status(id, Status::Complete); - success(task_msg("completed", &task, id)); - } - Commands::Start(StartTask { id }) => { - let task = get_task(&mut tasks.clone(), id); - - tasks.set_status(id, Status::Active); - success(task_msg("started", &task, id)); - } - Commands::Stop(StopTask { id }) => { - let task = get_task(&mut tasks.clone(), id); - - if task.when.is_none() { - tasks.set_status(id, Status::Inbox); - } else { - tasks.set_status(id, Status::Pending); - }; - success(task_msg("stopped", &task, id)); - } - Commands::Clear => { - tasks.clear(); - success(String::from("cleared all tasks")); - } _ => todo!() }; tasks diff --git a/src/main.rs b/src/main.rs index 7a3d287..0498315 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ fn main() { // If the tasks file doesn't exist, create it first if !Path::new(&tasks_file_path).exists() { - println!("warning: file '~/.local/share/tasks' does not exist. creating.."); + cli::warning("file '~/.local/share/tasks' does not exist. creating.."); let tasks = Tasks::new(&tasks_file_path); data::save_tasks(&tasks_file_path, &tasks).unwrap(); }; diff --git a/src/tasks.rs b/src/tasks.rs index 0fe0944..f7ca48e 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,13 +1,8 @@ use serde::{Deserialize, Serialize}; -use chrono::{DateTime, Utc}; +use chrono::{NaiveDateTime, Utc}; use colored::*; -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Tag { - title: String, // The required title of the tag -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum Status { Inbox, @@ -20,7 +15,7 @@ impl Status { pub fn as_string(&self) -> ColoredString { match self { Status::Inbox => "📮 Inbox".blue(), - Status::Pending => "🗓️ Pending".yellow(), + Status::Pending => "📅 Pending".yellow(), Status::Active => "✍️ Active".red(), Status::Complete => "📗 Complete".green(), } @@ -32,11 +27,11 @@ pub struct Task { pub title: String, // The required title of the task pub status: Status, // Current status of the task pub notes: Option, // Any notes to explain the task - pub tags: Option>, // Tasks can be tagged for organisation + pub tags: Option>, // Tasks can be tagged for organisation pub subtasks: Option>, // Tasks can be hierarchically split into subtasks - pub when: Option>, // The date you want to do the task - pub deadline: Option>, // The latest date the task should be done - pub reminder: Option>, // The datetime a reminder will alert you + pub when: Option, // The date you want to do the task + pub deadline: Option, // The latest date the task should be done + pub reminder: Option, // The datetime a reminder will alert you } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -46,16 +41,22 @@ pub struct Tasks { } impl Task { - pub fn new(title: String) -> Self { + pub fn new(title: String, notes: Option, tags: Option>, when: Option, deadline: Option, reminder: Option) -> Self { + let status = if when.is_some() { + Status::Pending + } else { + Status::Inbox + }; + Self { title, - status: Status::Inbox, - notes: None, - tags: None, + status, + notes, + tags, subtasks: None, - when: None, - deadline: None, - reminder: None, + when, + deadline, + reminder, } } }