From 8bbe32fb6ca226d54995c4ac0ed0fe7a59909c17 Mon Sep 17 00:00:00 2001 From: aaron-jack-manning Date: Wed, 24 Aug 2022 22:09:52 +1000 Subject: [PATCH] change to some option flags; addition of due dates; changes to display of tasks --- README.md | 7 ++--- src/colour.rs | 23 +++++++++++++++ src/main.rs | 15 ++++++---- src/state.rs | 4 +++ src/tasks.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 110 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 07570f7..746d8d6 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ USAGE: toru OPTIONS: - -H, --help Print help information + -h, --help Print help information -V, --version Print version information SUBCOMMANDS: @@ -73,7 +73,7 @@ SUBCOMMANDS: view Displays the specified task in detail ``` -You can view any help screen by passing in the `-H` or `--help` flag, and the internal documentation is designed to make it obvious how to use Toru. +You can view any help screen by passing in the `-h` or `--help` flag, and the internal documentation is designed to make it obvious how to use Toru. To start up you will need a vault to store tasks in, which you can create by running `toru vault new `. @@ -95,9 +95,6 @@ Then you can run `toru new` to create your first task. - Error if any circular dependencies are introduced - Make sure dependencies written to file are only those that could be successfully created - List dependencies as a tree on note view below info -- Due dates - - Taken as input when creating notes - - Displayed in list view by default (with number of days remaining) - Completed Date - Keep track of completed date, and correctly update upon marking as complete or manual edit - Disallow removing it in a manual edit unless complete is also marked to false diff --git a/src/colour.rs b/src/colour.rs index 48705fc..7dd6e7f 100644 --- a/src/colour.rs +++ b/src/colour.rs @@ -34,3 +34,26 @@ pub fn id(text : &str) -> colored::ColoredString { pub fn greyed_out(text : &str) -> colored::ColoredString { text.truecolor(99, 110, 114) } + + +pub mod due_date { + use colored::Colorize; + + pub fn overdue(text : &str) -> colored::ColoredString { + text.truecolor(192, 57, 43) + } + + pub fn very_close(text : &str) -> colored::ColoredString { + text.truecolor(231, 76, 60) + + } + + pub fn close(text : &str) -> colored::ColoredString { + text.truecolor(230, 126, 34) + + } + + pub fn plenty_of_time(text : &str) -> colored::ColoredString { + text.truecolor(46, 204, 113) + } +} diff --git a/src/main.rs b/src/main.rs index c647588..3363326 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ struct Args { } #[derive(clap::Subcommand, Debug, PartialEq, Eq)] -#[clap(version, help_short='H', about, author, global_setting=clap::AppSettings::DisableHelpSubcommand)] +#[clap(version, help_short='h', about, author, global_setting=clap::AppSettings::DisableHelpSubcommand)] enum Command { /// Create a new task. New { @@ -33,6 +33,9 @@ enum Command { dependencies : Vec, #[clap(short, long, value_enum)] priority : Option, + /// Due date, expecting format yyyy-mm-ddThh:mm:ss + #[clap(long)] + due : Option, }, /// Displays the specified task in detail. View { @@ -83,9 +86,9 @@ enum Command { /// For tracking time against a note. Track { id_or_name : String, - #[clap(short, default_value_t=0)] + #[clap(short='H', default_value_t=0)] hours : u16, - #[clap(short, default_value_t=0)] + #[clap(short='M', default_value_t=0)] minutes : u16, }, /// For statistics about the state of your vault. @@ -226,7 +229,7 @@ fn program() -> Result<(), error::Error> { else if command == GitIgnore { let vault_folder = &config.current_vault()?.1; vcs::create_gitignore(vault_folder)?; - println!("Default .gitignore file created"); + println!("Default {} file created", colour::file(".gitignore")); } else if let Svn { args } = command { let vault_folder = &config.current_vault()?.1; @@ -238,8 +241,8 @@ fn program() -> Result<(), error::Error> { let mut state = state::State::load(vault_folder)?; match command { - New { name, info, tags, dependencies, priority } => { - let task = tasks::Task::new(name, info, tags, dependencies, priority, vault_folder, &mut state)?; + New { name, info, tags, dependencies, priority, due } => { + let task = tasks::Task::new(name, info, tags, dependencies, priority, due, vault_folder, &mut state)?; println!("Created task {} (ID: {})", colour::task_name(&task.data.name), colour::id(&task.data.id.to_string())); }, Delete { id_or_name } => { diff --git a/src/state.rs b/src/state.rs index 0abcf98..3aa685b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -22,6 +22,8 @@ pub struct InternalState { pub next_id : Id, #[serde_as(as = "HashMap")] pub index : HashMap>, + //#[serde_as(as = "HashMap")] + //pub dependencies : HashMap>, } impl State { @@ -70,6 +72,8 @@ impl State { } } + // + let data = InternalState { next_id : u64::try_from(max_id + 1).unwrap(), index, diff --git a/src/tasks.rs b/src/tasks.rs index 4592457..ff89fc5 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -11,6 +11,7 @@ use std::path; use std::io::{Write, Seek}; use std::collections::HashSet; use colored::Colorize; +use chrono::SubsecRound; pub type Id = u64; @@ -73,7 +74,7 @@ pub struct InternalTask { pub tags : HashSet, pub dependencies : HashSet, pub priority : Priority, - //due : Option, + pub due : Option, pub created : chrono::NaiveDateTime, pub complete : bool, pub discarded : bool, @@ -82,7 +83,7 @@ pub struct InternalTask { } impl Task { - pub fn new(name : String, info : Option, tags : Vec, dependencies : Vec, priority : Option, vault_folder : &path::Path, state : &mut state::State) -> Result { + pub fn new(name : String, info : Option, tags : Vec, dependencies : Vec, priority : Option, due : Option, vault_folder : &path::Path, state : &mut state::State) -> Result { if name.chars().all(|c| c.is_numeric()) { return Err(error::Error::Generic(String::from("Name must not be purely numeric"))); @@ -105,8 +106,9 @@ impl Task { tags : tags.into_iter().collect(), dependencies : dependencies.into_iter().collect(), priority : priority.unwrap_or_default(), + due, time_entries : Vec::new(), - created : chrono::Utc::now().naive_local(), + created : chrono::Local::now().naive_local(), complete : false, discarded : false, }; @@ -243,7 +245,12 @@ impl Task { println!("Priority: {}", self.data.priority.coloured()); println!("Tags: [{}]", format_hash_set(&self.data.tags)?); - println!("Created: {}", self.data.created); + println!("Created: {}", self.data.created.round_subsecs(0)); + + if let Some(due) = self.data.due { + let due = format_due_date(&due, !self.data.complete, true); + println!("Due: {}", due); + } if let Some(mut info) = self.data.info.clone() { let mut max_line_width = 0; @@ -294,6 +301,65 @@ fn format_hash_set(set : &HashSet) -> Result String { + let remaining = *due - chrono::Local::now().naive_local(); + + let fuzzy_period = if remaining.num_days() != 0 { + let days = remaining.num_days().abs(); + format!("{} day{}", days, if days == 1 {""} else {"s"}) + } + else if remaining.num_hours() != 0 { + let hours = remaining.num_hours().abs(); + format!("{} hour{}", hours, if hours == 1 {""} else {"s"}) + } + else if remaining.num_minutes() != 0 { + let minutes = remaining.num_minutes().abs(); + format!("{} minute{}", minutes, if minutes == 1 {""} else {"s"}) + } + else { + let seconds = remaining.num_seconds().abs(); + format!("{} second{}", seconds, if seconds == 1 {""} else {"s"}) + }; + + if include_fuzzy_period { + if colour { + if remaining < chrono::Duration::zero() { + format!("{} {}", due.round_subsecs(0), colour::due_date::overdue(&format!("({} overdue)", fuzzy_period))) + } + else if remaining < chrono::Duration::days(1) { + format!("{} {}", due.round_subsecs(0), colour::due_date::very_close(&format!("({} remaining)", fuzzy_period))) + + } + else if remaining < chrono::Duration::days(3) { + format!("{} {}", due.round_subsecs(0), colour::due_date::close(&format!("({} remaining)", fuzzy_period))) + + } + else { + format!("{} {}", due.round_subsecs(0), colour::due_date::plenty_of_time(&format!("({} remaining)", fuzzy_period))) + } + } + else { + if remaining < chrono::Duration::zero() { + format!("{} {}", due.round_subsecs(0), format!("({} overdue)", fuzzy_period)) + } + else if remaining < chrono::Duration::days(1) { + format!("{} {}", due.round_subsecs(0), format!("({} remaining)", fuzzy_period)) + + } + else if remaining < chrono::Duration::days(3) { + format!("{} {}", due.round_subsecs(0), format!("({} remaining)", fuzzy_period)) + + } + else { + format!("{} {}", due.round_subsecs(0), format!("({} remaining)", fuzzy_period)) + } + } + } + else { + format!("{}", due.round_subsecs(0)) + } +} + pub fn list(vault_folder : &path::Path) -> Result<(), error::Error> { let mut table = comfy_table::Table::new(); @@ -301,7 +367,7 @@ pub fn list(vault_folder : &path::Path) -> Result<(), error::Error> { .load_preset(comfy_table::presets::UTF8_FULL) .apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS) .set_content_arrangement(comfy_table::ContentArrangement::Dynamic); - table.set_header(vec!["Id", "Name", "Tags", "Priority", "Time"]); + table.set_header(vec!["Id", "Name", "Tags", "Priority", "Tracked", "Due"]); let mut tasks = Task::load_all(vault_folder, true)?; tasks.sort_by(|t1, t2| t2.data.priority.cmp(&t1.data.priority)); @@ -318,6 +384,7 @@ pub fn list(vault_folder : &path::Path) -> Result<(), error::Error> { format_hash_set(&task.data.tags)?, task.data.priority.to_string(), if duration == Duration::zero() { String::new() } else { duration.to_string() }, + match task.data.due { Some(due) => format_due_date(&due, !task.data.complete, false), None => String::new() }, ] ); }