change to some option flags; addition of due dates; changes to display of tasks
This commit is contained in:
parent
0022f8de53
commit
8bbe32fb6c
@ -51,7 +51,7 @@ USAGE:
|
|||||||
toru <SUBCOMMAND>
|
toru <SUBCOMMAND>
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-H, --help Print help information
|
-h, --help Print help information
|
||||||
-V, --version Print version information
|
-V, --version Print version information
|
||||||
|
|
||||||
SUBCOMMANDS:
|
SUBCOMMANDS:
|
||||||
@ -73,7 +73,7 @@ SUBCOMMANDS:
|
|||||||
view Displays the specified task in detail
|
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 <NAME> <PATH>`.
|
To start up you will need a vault to store tasks in, which you can create by running `toru vault new <NAME> <PATH>`.
|
||||||
|
|
||||||
@ -95,9 +95,6 @@ Then you can run `toru new` to create your first task.
|
|||||||
- Error if any circular dependencies are introduced
|
- Error if any circular dependencies are introduced
|
||||||
- Make sure dependencies written to file are only those that could be successfully created
|
- Make sure dependencies written to file are only those that could be successfully created
|
||||||
- List dependencies as a tree on note view below info
|
- 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
|
- Completed Date
|
||||||
- Keep track of completed date, and correctly update upon marking as complete or manual edit
|
- 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
|
- Disallow removing it in a manual edit unless complete is also marked to false
|
||||||
|
@ -34,3 +34,26 @@ pub fn id(text : &str) -> colored::ColoredString {
|
|||||||
pub fn greyed_out(text : &str) -> colored::ColoredString {
|
pub fn greyed_out(text : &str) -> colored::ColoredString {
|
||||||
text.truecolor(99, 110, 114)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
15
src/main.rs
15
src/main.rs
@ -19,7 +19,7 @@ struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Subcommand, Debug, PartialEq, Eq)]
|
#[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 {
|
enum Command {
|
||||||
/// Create a new task.
|
/// Create a new task.
|
||||||
New {
|
New {
|
||||||
@ -33,6 +33,9 @@ enum Command {
|
|||||||
dependencies : Vec<Id>,
|
dependencies : Vec<Id>,
|
||||||
#[clap(short, long, value_enum)]
|
#[clap(short, long, value_enum)]
|
||||||
priority : Option<tasks::Priority>,
|
priority : Option<tasks::Priority>,
|
||||||
|
/// Due date, expecting format yyyy-mm-ddThh:mm:ss
|
||||||
|
#[clap(long)]
|
||||||
|
due : Option<chrono::NaiveDateTime>,
|
||||||
},
|
},
|
||||||
/// Displays the specified task in detail.
|
/// Displays the specified task in detail.
|
||||||
View {
|
View {
|
||||||
@ -83,9 +86,9 @@ enum Command {
|
|||||||
/// For tracking time against a note.
|
/// For tracking time against a note.
|
||||||
Track {
|
Track {
|
||||||
id_or_name : String,
|
id_or_name : String,
|
||||||
#[clap(short, default_value_t=0)]
|
#[clap(short='H', default_value_t=0)]
|
||||||
hours : u16,
|
hours : u16,
|
||||||
#[clap(short, default_value_t=0)]
|
#[clap(short='M', default_value_t=0)]
|
||||||
minutes : u16,
|
minutes : u16,
|
||||||
},
|
},
|
||||||
/// For statistics about the state of your vault.
|
/// For statistics about the state of your vault.
|
||||||
@ -226,7 +229,7 @@ fn program() -> Result<(), error::Error> {
|
|||||||
else if command == GitIgnore {
|
else if command == GitIgnore {
|
||||||
let vault_folder = &config.current_vault()?.1;
|
let vault_folder = &config.current_vault()?.1;
|
||||||
vcs::create_gitignore(vault_folder)?;
|
vcs::create_gitignore(vault_folder)?;
|
||||||
println!("Default .gitignore file created");
|
println!("Default {} file created", colour::file(".gitignore"));
|
||||||
}
|
}
|
||||||
else if let Svn { args } = command {
|
else if let Svn { args } = command {
|
||||||
let vault_folder = &config.current_vault()?.1;
|
let vault_folder = &config.current_vault()?.1;
|
||||||
@ -238,8 +241,8 @@ fn program() -> Result<(), error::Error> {
|
|||||||
let mut state = state::State::load(vault_folder)?;
|
let mut state = state::State::load(vault_folder)?;
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
New { name, info, tags, dependencies, priority } => {
|
New { name, info, tags, dependencies, priority, due } => {
|
||||||
let task = tasks::Task::new(name, info, tags, dependencies, priority, vault_folder, &mut state)?;
|
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()));
|
println!("Created task {} (ID: {})", colour::task_name(&task.data.name), colour::id(&task.data.id.to_string()));
|
||||||
},
|
},
|
||||||
Delete { id_or_name } => {
|
Delete { id_or_name } => {
|
||||||
|
@ -22,6 +22,8 @@ pub struct InternalState {
|
|||||||
pub next_id : Id,
|
pub next_id : Id,
|
||||||
#[serde_as(as = "HashMap<DisplayFromStr, _>")]
|
#[serde_as(as = "HashMap<DisplayFromStr, _>")]
|
||||||
pub index : HashMap<String, Vec<Id>>,
|
pub index : HashMap<String, Vec<Id>>,
|
||||||
|
//#[serde_as(as = "HashMap<DisplayFromStr, _>")]
|
||||||
|
//pub dependencies : HashMap<Id, Vec<Id>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@ -70,6 +72,8 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
let data = InternalState {
|
let data = InternalState {
|
||||||
next_id : u64::try_from(max_id + 1).unwrap(),
|
next_id : u64::try_from(max_id + 1).unwrap(),
|
||||||
index,
|
index,
|
||||||
|
77
src/tasks.rs
77
src/tasks.rs
@ -11,6 +11,7 @@ use std::path;
|
|||||||
use std::io::{Write, Seek};
|
use std::io::{Write, Seek};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use chrono::SubsecRound;
|
||||||
|
|
||||||
pub type Id = u64;
|
pub type Id = u64;
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ pub struct InternalTask {
|
|||||||
pub tags : HashSet<String>,
|
pub tags : HashSet<String>,
|
||||||
pub dependencies : HashSet<Id>,
|
pub dependencies : HashSet<Id>,
|
||||||
pub priority : Priority,
|
pub priority : Priority,
|
||||||
//due : Option<chrono::NaiveDateTime>,
|
pub due : Option<chrono::NaiveDateTime>,
|
||||||
pub created : chrono::NaiveDateTime,
|
pub created : chrono::NaiveDateTime,
|
||||||
pub complete : bool,
|
pub complete : bool,
|
||||||
pub discarded : bool,
|
pub discarded : bool,
|
||||||
@ -82,7 +83,7 @@ pub struct InternalTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
pub fn new(name : String, info : Option<String>, tags : Vec<String>, dependencies : Vec<Id>, priority : Option<Priority>, vault_folder : &path::Path, state : &mut state::State) -> Result<Self, error::Error> {
|
pub fn new(name : String, info : Option<String>, tags : Vec<String>, dependencies : Vec<Id>, priority : Option<Priority>, due : Option<chrono::NaiveDateTime>, vault_folder : &path::Path, state : &mut state::State) -> Result<Self, error::Error> {
|
||||||
|
|
||||||
if name.chars().all(|c| c.is_numeric()) {
|
if name.chars().all(|c| c.is_numeric()) {
|
||||||
return Err(error::Error::Generic(String::from("Name must not be purely 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(),
|
tags : tags.into_iter().collect(),
|
||||||
dependencies : dependencies.into_iter().collect(),
|
dependencies : dependencies.into_iter().collect(),
|
||||||
priority : priority.unwrap_or_default(),
|
priority : priority.unwrap_or_default(),
|
||||||
|
due,
|
||||||
time_entries : Vec::new(),
|
time_entries : Vec::new(),
|
||||||
created : chrono::Utc::now().naive_local(),
|
created : chrono::Local::now().naive_local(),
|
||||||
complete : false,
|
complete : false,
|
||||||
discarded : false,
|
discarded : false,
|
||||||
};
|
};
|
||||||
@ -243,7 +245,12 @@ impl Task {
|
|||||||
|
|
||||||
println!("Priority: {}", self.data.priority.coloured());
|
println!("Priority: {}", self.data.priority.coloured());
|
||||||
println!("Tags: [{}]", format_hash_set(&self.data.tags)?);
|
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() {
|
if let Some(mut info) = self.data.info.clone() {
|
||||||
let mut max_line_width = 0;
|
let mut max_line_width = 0;
|
||||||
@ -294,6 +301,65 @@ fn format_hash_set<T : fmt::Display>(set : &HashSet<T>) -> Result<String, error:
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_due_date(due : &chrono::NaiveDateTime, include_fuzzy_period : bool, colour : bool) -> 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> {
|
pub fn list(vault_folder : &path::Path) -> Result<(), error::Error> {
|
||||||
|
|
||||||
let mut table = comfy_table::Table::new();
|
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)
|
.load_preset(comfy_table::presets::UTF8_FULL)
|
||||||
.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS)
|
.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS)
|
||||||
.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
|
.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)?;
|
let mut tasks = Task::load_all(vault_folder, true)?;
|
||||||
tasks.sort_by(|t1, t2| t2.data.priority.cmp(&t1.data.priority));
|
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)?,
|
format_hash_set(&task.data.tags)?,
|
||||||
task.data.priority.to_string(),
|
task.data.priority.to_string(),
|
||||||
if duration == Duration::zero() { String::new() } else { duration.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() },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user