diff --git a/src/cli.rs b/src/cli.rs index cd09684..0c795f3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,12 +1,14 @@ -use chrono::NaiveDateTime; +use chrono::{NaiveDateTime, Local}; use crate::args::{TasksArgs, Commands}; use crate::args::{CreateTask, DeleteTask, ShowTask, StartTask, StopTask, CompleteTask}; -use crate::tasks::{Tasks, Task, Status}; +use crate::tasks::{Tasks, Task, Status, TasksError}; use prettytable::{Table, Row, row, format}; use colored::*; use fuzzydate; +use std::panic; -pub fn success(msg: String) { + +fn success(msg: String) { println!("{} {}", "success:".green().bold(), msg); } @@ -14,26 +16,20 @@ pub fn warning(msg: &str) { println!("{} {}", "warning:".yellow().bold(), msg); } -#[allow(dead_code)] -pub fn error(msg: String) { - println!("{} {}", "error:".red().bold(), msg); - panic!(); +pub fn info(msg: &str) { + println!("{} {}", "info:".blue().bold(), msg); } fn task_msg(msg: &str, task: &Task, id: usize) -> String { format!("{} task: {}({})", msg, task.title.blue(), id.to_string().cyan()) } -fn get_task(tasks: &mut Tasks, id: usize) -> Task { - match tasks.get_task(id) { - Ok(task) => task.clone(), - Err(error) => panic!("error: {}", error), - } -} - fn parse_date(date_string: Option) -> Option { if date_string.is_some() { - Some(fuzzydate::parse(date_string.unwrap()).unwrap()) + match fuzzydate::parse(date_string.unwrap()) { + Ok(date) => Some(date), + Err(err) => panic!("{:?}", err), + } } else { None } @@ -44,12 +40,23 @@ fn calc_row(task: &Task, id: usize) -> Row { // Generate greyed out rows for complete tasks Row::from([id.to_string().bright_black().italic(), task.status.as_string().bright_black().italic(), + "N/A".bright_black(), task.title.clone().bright_black().italic()]) + } else { let when = if task.when.is_some() { - format!("{}", task.when.unwrap().format("%Y-%m-%d")).bright_black() + let date = format!("{}", task.when.unwrap().format("%Y-%m-%d")); + let now = Local::now().date_naive(); + + if now == task.when.unwrap().date() { + date.bright_red() + } else if now.succ_opt().unwrap() == task.when.unwrap().date() { + date.yellow() + } else { + date.white() + } } else { - String::from("N/A").bright_black() + "N/A".bright_black() }; // Generate normal colored rows for uncompleted tasks @@ -57,7 +64,7 @@ fn calc_row(task: &Task, id: usize) -> Row { } } -pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> &mut Tasks { +pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, TasksError> { match arguments.command { Commands::Add(CreateTask { title, notes, tags, when, deadline, reminder, ..}) => { let when = parse_date(when); @@ -70,53 +77,58 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> &mut Tasks { }; let task = Task::new(title, notes, tags, when, deadline, reminder); - tasks.add(task.clone()); + tasks.push(task.clone()); let id = tasks.len() - 1; success(task_msg("created", &task, id)); + + Ok(tasks) } Commands::Del(DeleteTask { id }) => { - let task = get_task(tasks, id); + let mut binding = tasks.clone(); + let task = binding.get_task(id)?; + tasks.remove(id)?; - tasks.del(id); success(task_msg("deleted", &task, id)); + Ok(tasks) } Commands::Done(CompleteTask { id }) => { - let task = get_task(&mut tasks.clone(), id); + let task = tasks.get_task(id)?; + task.complete(); - tasks.set_status(id, Status::Complete); success(task_msg("completed", &task, id)); + Ok(tasks) } Commands::Start(StartTask { id }) => { - let task = get_task(&mut tasks.clone(), id); + let task = tasks.get_task(id)?; + task.start(); - tasks.set_status(id, Status::Active); success(task_msg("started", &task, id)); + Ok(tasks) } Commands::Stop(StopTask { id }) => { - let task = get_task(&mut tasks.clone(), id); + let task = tasks.get_task(id)?; + task.stop(); - if task.when.is_none() { - tasks.set_status(id, Status::Inbox); - } else { - tasks.set_status(id, Status::Pending); - }; success(task_msg("stopped", &task, id)); + Ok(tasks) } Commands::Clear => { - tasks.clear(); + tasks.clear()?; + success(String::from("cleared all tasks")); + Ok(tasks) } Commands::Show(ShowTask { id }) => { if id.is_none() { - if tasks.tasks.is_none() { - warning("no tasks available to show") + if tasks.is_empty() { + info("no tasks found") } else { // Create the table for printing let mut table = Table::new(); @@ -134,8 +146,9 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> &mut Tasks { println!("{}", table); }; } else { + // Get the task let id = id.unwrap(); - let task = get_task(&mut tasks.clone(), id); + let task = tasks.get_task(id)?; // Generate and print the table let mut table = Table::new(); @@ -144,9 +157,10 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> &mut Tasks { table.add_row(calc_row(&task, id)); println!("{}", table) }; + + Ok(tasks) } _ => todo!() - }; - tasks -} \ No newline at end of file + } +} diff --git a/src/main.rs b/src/main.rs index 0498315..4a7dc33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use crate::tasks::Tasks; use crate::args::TasksArgs; use std::path::Path; use clap::Parser; +use colored::*; fn main() { // Generate the file path for tasks @@ -22,12 +23,15 @@ fn main() { // Load tasks and check for any errors when loading the tasks let mut tasks = match data::load_tasks(&tasks_file_path) { Ok(tasks) => tasks, - Err(_error) => panic!("error: couldn't open file {} - likely corrupted", &tasks_file_path), + Err(error) => panic!("{} {:?}", "error:".red().bold(), error), }; // Parse command line arguments let arguments = TasksArgs::parse(); - let tasks = cli::execute(&mut tasks, arguments); + let tasks = match cli::execute(&mut tasks, arguments) { + Ok(tasks) => tasks, + Err(error) => panic!("{} {:?}", "error:".red().bold(), error), + }; // Save any changes data::save_tasks(tasks_file_path, &tasks).unwrap() diff --git a/src/tasks.rs b/src/tasks.rs index f7ca48e..9c72a4d 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,8 +1,11 @@ use serde::{Deserialize, Serialize}; -use chrono::{NaiveDateTime, Utc}; +use chrono::NaiveDateTime; use colored::*; +#[derive(Debug)] +pub struct TasksError(String); + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum Status { Inbox, @@ -11,17 +14,6 @@ pub enum Status { Complete, } -impl Status { - pub fn as_string(&self) -> ColoredString { - match self { - Status::Inbox => "📮 Inbox".blue(), - Status::Pending => "📅 Pending".yellow(), - Status::Active => "✍️ Active".red(), - Status::Complete => "📗 Complete".green(), - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Task { pub title: String, // The required title of the task @@ -41,24 +33,29 @@ pub struct Tasks { } impl Task { - 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 - }; + 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, - notes, - tags, - subtasks: None, - when, - deadline, - reminder, + Self { title, status, notes, tags, subtasks: None, when, deadline, reminder, } + } + + pub fn start(&mut self) { + self.status = Status::Active; + } + + pub fn stop(&mut self) { + if self.when.is_none() { + self.status = Status::Inbox; + } else { + self.status = Status::Pending; } } + + pub fn complete(&mut self) { + self.status = Status::Complete; + } } impl Tasks { @@ -69,43 +66,78 @@ impl Tasks { } } - pub fn get_task(&mut self, id: usize) -> Result<&mut Task, &str> { - if self.tasks.is_none() { - Err("there are no tasks") + fn task_not_found(&self, id: usize) -> TasksError { + TasksError(format!("couldn't find task with id {}", id)) + } + + fn task_exists(&self, id: usize) -> bool{ + if id >= self.len() { false } else { true } + } + + pub fn is_empty(&self) -> bool { + if self.len() == 0 { + true } else { - if id >= self.tasks.as_ref().unwrap().len() { - Err("couldn't find task") - } else { + false + } + } + + pub fn get_task(&mut self, id: usize) -> Result<&mut Task, TasksError> { + if self.is_empty() { + Err(TasksError(format!("no tasks available"))) + } else { + if self.task_exists(id) { let task = &mut self.tasks.as_mut().unwrap()[id]; Ok(task) + } else { + Err(TasksError(format!("couldn't find task with id {}", id))) } } } - #[allow(dead_code)] - pub fn set_status(&mut self, id: usize, status: Status) { - let mut task: &mut Task = self.get_task(id).unwrap(); - task.status = status; - } - - pub fn add(&mut self, task: Task) { - if self.tasks.is_none() { + pub fn push(&mut self, task: Task) { + if self.is_empty() { self.tasks = Some(vec![task]); } else { self.tasks.as_mut().unwrap().push(task); }; } - pub fn del(&mut self, id: usize) { - self.tasks.as_mut().unwrap().remove(id); + pub fn remove(&mut self, id: usize) -> Result<(), TasksError> { + if self.task_exists(id) { + self.tasks.as_mut().unwrap().remove(id); + Ok(()) + } else { + Err(self.task_not_found(id)) + } } pub fn len(&self) -> usize { - self.tasks.as_ref().unwrap().len() + if self.tasks.is_none() { + 0 + } else { + self.tasks.as_ref().unwrap().len() + } } - pub fn clear(&mut self) { - self.tasks = None; + pub fn clear(&mut self) -> Result<(), TasksError> { + if self.is_empty() { + Err(TasksError(String::from("no tasks available"))) + } else { + self.tasks = None; + Ok(()) + } + } +} + +impl Status { + pub fn as_string(&self) -> ColoredString { + match self { + Status::Inbox => "📮 Inbox".blue(), + Status::Pending => "📅 Pending".yellow(), + Status::Active => "✍️ Active".red(), + Status::Complete => "📗 Complete".green(), + } } }