🧰 Fixes and final important features

This commit is contained in:
Maddie H 2023-02-24 21:33:01 +00:00
parent 102123b688
commit c08af0172c
No known key found for this signature in database
GPG Key ID: 64FAA9959751687D
5 changed files with 214 additions and 79 deletions

View File

@ -100,8 +100,36 @@ pub struct EditTask {
pub struct ModifyTask { pub struct ModifyTask {
/// ID of the task /// ID of the task
pub id: usize, pub id: usize,
/// Summary of the task
pub summary: String, /// Title of the task
#[arg(short, long)]
#[clap(default_value=None)]
pub title: Option<String>,
/// Any notes to help explain/remember the task
#[arg(short, long)]
#[clap(default_value=None)]
pub notes: Option<String>,
/// Tags for organisation, separated by commas
#[arg(short, long)]
#[clap(default_value=None)]
pub tags: Option<String>,
/// Date when you want to do the task
#[arg(short, long)]
#[clap(default_value=None)]
pub when: Option<String>,
/// Deadline when the task has to be in
#[arg(short, long)]
#[clap(default_value=None)]
pub deadline: Option<String>,
/// The date and time when you want to be reminded
#[arg(short, long)]
#[clap(default_value=None)]
pub reminder: Option<String>,
} }
#[derive(Args, PartialEq, Debug)] #[derive(Args, PartialEq, Debug)]
pub struct GitExecute { pub struct GitExecute {
@ -111,12 +139,12 @@ pub struct GitExecute {
#[derive(Args, PartialEq, Debug)] #[derive(Args, PartialEq, Debug)]
pub struct SyncTasks { pub struct SyncTasks {
/// Git remote to use /// Git remote to use
#[clap(default_value="origin")] #[clap(default_value = "origin")]
pub remote: String, pub remote: String,
} }
#[derive(Args, PartialEq, Debug)] #[derive(Args, PartialEq, Debug)]
pub struct UndoExecute { pub struct UndoExecute {
/// Number of times to undo /// Number of times to undo
#[clap(default_value="1")] #[clap(default_value = "1")]
pub number: String, pub number: String,
} }

View File

@ -1,72 +1,98 @@
use chrono::{NaiveDateTime, Local}; use crate::args::{Commands, TasksArgs};
use crate::args::{TasksArgs, Commands}; use crate::args::{CompleteTask, CreateTask, DeleteTask, ShowTask, StartTask, StopTask, ModifyTask};
use crate::args::{CreateTask, DeleteTask, ShowTask, StartTask, StopTask, CompleteTask}; use crate::tasks::{Status, Task, Tasks, TasksError};
use crate::tasks::{Tasks, Task, Status, TasksError}; use chrono::{Local, NaiveDateTime};
use prettytable::{Table, Row, row, format};
use colored::*; use colored::*;
use fuzzydate; use fuzzydate;
use prettytable::{format, row, Row, Table};
use std::panic; use std::panic;
fn success(msg: String) {
println!("{} {}", "success:".green().bold(), msg);
}
pub fn warning(msg: &str) { pub fn warning(msg: &str) {
println!("{} {}", "warning:".yellow().bold(), msg); println!("{} {}", "warning:".yellow().bold(), msg);
} }
pub fn info(msg: &str) { pub fn info(msg: String) {
println!("{} {}", "info:".blue().bold(), msg); println!("{} {}", "info:".blue().bold(), msg);
} }
fn success(msg: String) {
println!("{} {}", "success:".green().bold(), msg);
}
fn task_msg(msg: &str, task: &Task, id: usize) -> String { fn task_msg(msg: &str, task: &Task, id: usize) -> String {
format!("{} task: {}({})", msg, task.title.blue(), id.to_string().cyan()) format!(
"{} task: {}({})",
msg,
task.title.blue(),
id.to_string().cyan()
)
} }
fn parse_date(date_string: Option<String>) -> Option<NaiveDateTime> { fn parse_date(date_string: Option<String>) -> Option<NaiveDateTime> {
if date_string.is_some() { if date_string.is_some() {
match fuzzydate::parse(date_string.unwrap()) { match fuzzydate::parse(date_string.unwrap()) {
Ok(date) => Some(date), Ok(date) => Some(date),
Err(err) => panic!("{:?}", err), Err(err) => panic!("{} {:?}", "error:".red().bold(), err),
} }
} else { } else {
None None
} }
} }
fn calc_row(task: &Task, id: usize) -> Row { fn date_to_string(date: &Option<NaiveDateTime>) -> ColoredString {
if task.status == Status::Complete { if date.is_some() {
// Generate greyed out rows for complete tasks let date = date.unwrap().date();
Row::from([id.to_string().bright_black().italic(), let date_string = format!("{}", date.format("%Y-%m-%d"));
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() {
let date = format!("{}", task.when.unwrap().format("%Y-%m-%d"));
let now = Local::now().date_naive(); let now = Local::now().date_naive();
if now == task.when.unwrap().date() { if date <= now {
date.bright_red() // If the date is today or past today
} else if now.succ_opt().unwrap() == task.when.unwrap().date() { date_string.bright_red()
date.yellow() } else if now.succ_opt().unwrap() == date {
// If the date is tomorrow
date_string.yellow()
} else { } else {
date.white() // Otherwise the date is too far in the past
date_string.white()
} }
} else { } else {
"N/A".bright_black() "N/A".bright_black()
}; }
}
fn calc_row(task: &Task, id: usize) -> Row {
if task.status == Status::Complete {
// Generate greyed out rows for complete tasks
Row::from([
id.to_string().bright_black().italic(),
task.status.as_string().bright_black().italic(),
task.title.clone().bright_black().italic(),
date_to_string(&task.when).bright_black().italic(),
date_to_string(&task.deadline).bright_black().italic(),
])
} else {
// Generate normal colored rows for uncompleted tasks // Generate normal colored rows for uncompleted tasks
Row::from([id.to_string().cyan(), task.status.as_string(), when, task.title.clone().white()]) Row::from([
id.to_string().cyan(),
task.status.as_string(),
task.title.clone().white(),
date_to_string(&task.when),
date_to_string(&task.deadline),
])
} }
} }
pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, TasksError> { pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, TasksError> {
match arguments.command { match arguments.command {
Commands::Add(CreateTask { title, notes, tags, when, deadline, reminder, ..}) => { Commands::Add(CreateTask {
title,
notes,
tags,
when,
deadline,
reminder,
..
}) => {
let when = parse_date(when); let when = parse_date(when);
let deadline = parse_date(deadline); let deadline = parse_date(deadline);
let reminder = parse_date(reminder); let reminder = parse_date(reminder);
@ -80,8 +106,30 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, Ta
tasks.push(task.clone()); tasks.push(task.clone());
let id = tasks.len() - 1; let id = tasks.len() - 1;
success(task_msg("created", &task, id));
success(task_msg("created", &task, id));
Ok(tasks)
}
Commands::Modify(ModifyTask { id, title, notes, tags, when, deadline, reminder }) => {
let when = parse_date(when);
let deadline = parse_date(deadline);
let reminder = parse_date(reminder);
let tags: Option<Vec<String>> = if tags.is_some() {
Some(tags.unwrap().split(",").map(str::to_string).collect())
} else {
None
};
let task = tasks.get_task(id)?;
if title.is_some() {
info(task_msg("renaming task", &task, id))
};
task.modify(title, notes, tags, when, deadline, reminder);
success(task_msg("modified", &task, id));
Ok(tasks) Ok(tasks)
} }
@ -128,11 +176,17 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, Ta
Commands::Show(ShowTask { id }) => { Commands::Show(ShowTask { id }) => {
if id.is_none() { if id.is_none() {
if tasks.is_empty() { if tasks.is_empty() {
info("no tasks found") info(String::from("no tasks found"))
} else { } else {
// Create the table for printing // Create the table for printing
let mut table = Table::new(); let mut table = Table::new();
table.set_titles(row!["ID".magenta().bold(), "Status".magenta().bold(), "When".magenta().bold(), "Title".magenta().bold()]); table.set_titles(row![
"ID".magenta().bold(),
"Status".magenta().bold(),
"Title".magenta().bold(),
"When".magenta().bold(),
"Deadline".magenta().bold(),
]);
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
// Iterate through each task // Iterate through each task
@ -152,7 +206,10 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, Ta
// Generate and print the table // Generate and print the table
let mut table = Table::new(); let mut table = Table::new();
table.set_titles(row!["ID".magenta().bold(), "Status".magenta().bold(), "When".magenta().bold(), "Title".magenta().bold()]); table.set_titles(row![
"Item".magenta().bold(),
"Value".magenta().bold(),
]);
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.add_row(calc_row(&task, id)); table.add_row(calc_row(&task, id));
println!("{}", table) println!("{}", table)
@ -161,6 +218,6 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, Ta
Ok(tasks) Ok(tasks)
} }
_ => todo!() _ => todo!(),
} }
} }

View File

@ -1,12 +1,11 @@
use dirs::home_dir;
use serde_json;
use std::error::Error; use std::error::Error;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use dirs::home_dir;
use serde_json;
use crate::tasks::Tasks; use crate::tasks::Tasks;
const TASKS_FILE_PATH: &str = "/.local/share/tasks"; const TASKS_FILE_PATH: &str = "/.local/share/tasks";
pub fn save_tasks<P: AsRef<Path>>(path: P, tasks: &Tasks) -> Result<(), Box<dyn Error>> { pub fn save_tasks<P: AsRef<Path>>(path: P, tasks: &Tasks) -> Result<(), Box<dyn Error>> {
@ -30,5 +29,9 @@ pub fn load_tasks<P: AsRef<Path>>(path: P) -> Result<Tasks, Box<dyn Error>> {
} }
pub fn tasks_file_path() -> String { pub fn tasks_file_path() -> String {
format!("{}{}", home_dir().unwrap().to_str().unwrap(), TASKS_FILE_PATH) format!(
"{}{}",
home_dir().unwrap().to_str().unwrap(),
TASKS_FILE_PATH
)
} }

View File

@ -1,13 +1,13 @@
mod tasks;
mod args; mod args;
mod data;
mod cli; mod cli;
mod data;
mod tasks;
use crate::tasks::Tasks;
use crate::args::TasksArgs; use crate::args::TasksArgs;
use std::path::Path; use crate::tasks::Tasks;
use clap::Parser; use clap::Parser;
use colored::*; use colored::*;
use std::path::Path;
fn main() { fn main() {
// Generate the file path for tasks // Generate the file path for tasks

View File

@ -1,7 +1,6 @@
use serde::{Deserialize, Serialize};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use colored::*; use colored::*;
use serde::{Deserialize, Serialize};
#[derive(Debug)] #[derive(Debug)]
pub struct TasksError(String); pub struct TasksError(String);
@ -20,7 +19,6 @@ pub struct Task {
pub status: Status, // Current status of the task pub status: Status, // Current status of the task
pub notes: Option<String>, // Any notes to explain the task pub notes: Option<String>, // Any notes to explain the task
pub tags: Option<Vec<String>>, // Tasks can be tagged for organisation pub tags: Option<Vec<String>>, // Tasks can be tagged for organisation
pub subtasks: Option<Vec<Task>>, // Tasks can be hierarchically split into subtasks
pub when: Option<NaiveDateTime>, // The date you want to do the task pub when: Option<NaiveDateTime>, // The date you want to do the task
pub deadline: Option<NaiveDateTime>, // The latest date the task should be done pub deadline: Option<NaiveDateTime>, // The latest date the task should be done
pub reminder: Option<NaiveDateTime>, // The datetime a reminder will alert you pub reminder: Option<NaiveDateTime>, // The datetime a reminder will alert you
@ -32,13 +30,64 @@ pub struct Tasks {
pub tasks: Option<Vec<Task>>, // All the tasks in one vector pub tasks: Option<Vec<Task>>, // All the tasks in one vector
} }
impl Task { fn task_not_found(id: usize) -> TasksError {
pub fn new(title: String, notes: Option<String>, tags: Option<Vec<String>>, TasksError(format!("couldn't find task with id {}", id))
when: Option<NaiveDateTime>, deadline: Option<NaiveDateTime>, }
reminder: Option<NaiveDateTime>) -> Self {
let status = if when.is_some() { Status::Pending } else { Status::Inbox };
Self { title, status, notes, tags, subtasks: None, when, deadline, reminder, } fn no_tasks_available() -> TasksError {
TasksError(String::from("no tasks available"))
}
impl Task {
pub fn new(
title: String,
notes: Option<String>,
tags: Option<Vec<String>>,
when: Option<NaiveDateTime>,
deadline: Option<NaiveDateTime>,
reminder: Option<NaiveDateTime>,
) -> Self {
let status = if when.is_some() {
Status::Pending
} else {
Status::Inbox
};
Self {
title,
status,
notes,
tags,
when,
deadline,
reminder,
}
}
pub fn modify(&mut self, title: Option<String>, notes: Option<String>, tags: Option<Vec<String>>, when: Option<NaiveDateTime>, deadline: Option<NaiveDateTime>, reminder: Option<NaiveDateTime>) {
if title.is_some() {
self.title = title.unwrap();
};
if notes.is_some() {
self.notes = Some(notes.unwrap());
};
if tags.is_some() {
self.tags = Some(tags.unwrap());
};
if when.is_some() {
self.when = Some(when.unwrap());
};
if deadline.is_some() {
self.deadline = Some(deadline.unwrap());
};
if reminder.is_some() {
self.reminder = Some(reminder.unwrap());
};
} }
pub fn start(&mut self) { pub fn start(&mut self) {
@ -62,16 +111,16 @@ impl Tasks {
pub fn new(tasks_path: &str) -> Self { pub fn new(tasks_path: &str) -> Self {
Self { Self {
path: String::from(tasks_path), path: String::from(tasks_path),
tasks: None tasks: None,
} }
} }
fn task_not_found(&self, id: usize) -> TasksError { pub fn task_exists(&self, id: usize) -> bool {
TasksError(format!("couldn't find task with id {}", id)) if id >= self.len() {
false
} else {
true
} }
fn task_exists(&self, id: usize) -> bool{
if id >= self.len() { false } else { true }
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -84,13 +133,12 @@ impl Tasks {
pub fn get_task(&mut self, id: usize) -> Result<&mut Task, TasksError> { pub fn get_task(&mut self, id: usize) -> Result<&mut Task, TasksError> {
if self.is_empty() { if self.is_empty() {
Err(TasksError(format!("no tasks available"))) Err(no_tasks_available())
} else { } else {
if self.task_exists(id) { if self.task_exists(id) {
let task = &mut self.tasks.as_mut().unwrap()[id]; Ok(&mut self.tasks.as_mut().unwrap()[id])
Ok(task)
} else { } else {
Err(TasksError(format!("couldn't find task with id {}", id))) Err(task_not_found(id))
} }
} }
} }
@ -108,7 +156,7 @@ impl Tasks {
self.tasks.as_mut().unwrap().remove(id); self.tasks.as_mut().unwrap().remove(id);
Ok(()) Ok(())
} else { } else {
Err(self.task_not_found(id)) Err(task_not_found(id))
} }
} }
@ -122,7 +170,7 @@ impl Tasks {
pub fn clear(&mut self) -> Result<(), TasksError> { pub fn clear(&mut self) -> Result<(), TasksError> {
if self.is_empty() { if self.is_empty() {
Err(TasksError(String::from("no tasks available"))) Err(no_tasks_available())
} else { } else {
self.tasks = None; self.tasks = None;
Ok(()) Ok(())
@ -135,9 +183,8 @@ impl Status {
match self { match self {
Status::Inbox => "📮 Inbox".blue(), Status::Inbox => "📮 Inbox".blue(),
Status::Pending => "📅 Pending".yellow(), Status::Pending => "📅 Pending".yellow(),
Status::Active => "✍️ Active".red(), Status::Active => "🕑 Active".red(),
Status::Complete => "📗 Complete".green(), Status::Complete => "📗 Complete".green(),
} }
} }
} }