2022-08-24 10:38:47 +00:00
|
|
|
mod vcs;
|
2022-08-21 00:59:09 +00:00
|
|
|
mod edit;
|
2022-08-20 04:01:43 +00:00
|
|
|
mod vault;
|
2022-08-25 00:44:22 +00:00
|
|
|
mod index;
|
2022-08-20 04:01:43 +00:00
|
|
|
mod error;
|
|
|
|
mod tasks;
|
|
|
|
mod state;
|
2022-08-25 06:40:56 +00:00
|
|
|
mod graph;
|
2022-08-23 23:56:23 +00:00
|
|
|
mod stats;
|
2022-08-20 04:01:43 +00:00
|
|
|
mod config;
|
|
|
|
mod colour;
|
|
|
|
|
2022-08-21 00:59:09 +00:00
|
|
|
use tasks::Id;
|
|
|
|
|
2022-08-20 04:01:43 +00:00
|
|
|
use std::path;
|
|
|
|
|
|
|
|
#[derive(clap::Parser, Debug)]
|
|
|
|
struct Args {
|
|
|
|
#[clap(subcommand)]
|
|
|
|
command : Command,
|
|
|
|
}
|
|
|
|
|
2022-08-22 08:51:54 +00:00
|
|
|
#[derive(clap::Subcommand, Debug, PartialEq, Eq)]
|
2022-08-24 12:09:52 +00:00
|
|
|
#[clap(version, help_short='h', about, author, global_setting=clap::AppSettings::DisableHelpSubcommand)]
|
2022-08-20 04:01:43 +00:00
|
|
|
enum Command {
|
|
|
|
/// Create a new task.
|
|
|
|
New {
|
|
|
|
#[clap(short, long)]
|
|
|
|
name : String,
|
|
|
|
#[clap(short, long)]
|
|
|
|
info : Option<String>,
|
|
|
|
#[clap(short, long)]
|
2022-08-30 08:25:14 +00:00
|
|
|
tag : Vec<String>,
|
2022-08-20 04:01:43 +00:00
|
|
|
#[clap(short, long)]
|
2022-08-30 08:25:14 +00:00
|
|
|
dependency : Vec<Id>,
|
2022-08-20 04:01:43 +00:00
|
|
|
#[clap(short, long, value_enum)]
|
|
|
|
priority : Option<tasks::Priority>,
|
2022-08-24 12:09:52 +00:00
|
|
|
/// Due date, expecting format yyyy-mm-ddThh:mm:ss
|
|
|
|
#[clap(long)]
|
|
|
|
due : Option<chrono::NaiveDateTime>,
|
2022-08-20 04:01:43 +00:00
|
|
|
},
|
2022-08-20 23:58:05 +00:00
|
|
|
/// Displays the specified task in detail.
|
|
|
|
View {
|
2022-08-21 07:14:26 +00:00
|
|
|
id_or_name : String,
|
2022-08-21 00:59:09 +00:00
|
|
|
},
|
2022-08-24 22:30:52 +00:00
|
|
|
/// Edit a task directly.
|
2022-08-21 00:59:09 +00:00
|
|
|
Edit {
|
2022-08-21 07:14:26 +00:00
|
|
|
id_or_name : String,
|
2022-08-21 02:50:03 +00:00
|
|
|
/// Edit the info specifically in its own file.
|
|
|
|
#[clap(short, long)]
|
2022-08-21 02:10:31 +00:00
|
|
|
info : bool,
|
2022-08-20 23:58:05 +00:00
|
|
|
},
|
2022-08-25 06:40:56 +00:00
|
|
|
/// Delete a task (move file to trash).
|
2022-08-20 04:01:43 +00:00
|
|
|
Delete {
|
2022-08-21 07:14:26 +00:00
|
|
|
id_or_name : String,
|
2022-08-20 04:01:43 +00:00
|
|
|
},
|
|
|
|
/// Mark a task as complete.
|
|
|
|
Complete {
|
2022-08-21 07:14:26 +00:00
|
|
|
id_or_name : String,
|
2022-08-20 04:01:43 +00:00
|
|
|
},
|
2022-08-20 07:06:55 +00:00
|
|
|
/// Run Git commands at the root of the vault.
|
|
|
|
#[clap(trailing_var_arg=true)]
|
|
|
|
Git {
|
|
|
|
args : Vec<String>,
|
|
|
|
},
|
2022-08-24 10:38:47 +00:00
|
|
|
/// Run Subversion commands at the root of the vault.
|
|
|
|
#[clap(trailing_var_arg=true)]
|
|
|
|
Svn {
|
|
|
|
args : Vec<String>,
|
|
|
|
},
|
2022-08-22 08:51:54 +00:00
|
|
|
/// Adds the recommended .gitignore file to the vault.
|
|
|
|
#[clap(name="gitignore")]
|
|
|
|
GitIgnore,
|
2022-08-25 10:44:10 +00:00
|
|
|
/// Lists tasks according to the specified fields, ordering and filters.
|
2022-08-20 23:58:05 +00:00
|
|
|
List {
|
2022-08-25 10:44:10 +00:00
|
|
|
#[clap(flatten)]
|
|
|
|
options : ListOptions,
|
2022-08-20 23:58:05 +00:00
|
|
|
},
|
2022-08-24 22:30:52 +00:00
|
|
|
/// For tracking time against a task.
|
2022-08-22 10:27:17 +00:00
|
|
|
Track {
|
|
|
|
id_or_name : String,
|
2022-08-24 12:09:52 +00:00
|
|
|
#[clap(short='H', default_value_t=0)]
|
2022-08-22 10:27:17 +00:00
|
|
|
hours : u16,
|
2022-08-24 12:09:52 +00:00
|
|
|
#[clap(short='M', default_value_t=0)]
|
2022-08-22 10:27:17 +00:00
|
|
|
minutes : u16,
|
2022-08-30 08:23:23 +00:00
|
|
|
/// Date for the time entry [default: Today]
|
|
|
|
#[clap(short, long)]
|
|
|
|
date : Option<chrono::NaiveDate>,
|
|
|
|
/// Message to identify the time entry.
|
|
|
|
#[clap(short, long)]
|
|
|
|
message : Option<String>,
|
2022-08-22 10:27:17 +00:00
|
|
|
},
|
2022-08-23 23:56:23 +00:00
|
|
|
/// For statistics about the state of your vault.
|
|
|
|
#[clap(subcommand)]
|
|
|
|
Stats(StatsCommand),
|
2022-08-21 07:34:44 +00:00
|
|
|
/// For making changes to global configuration.
|
|
|
|
#[clap(subcommand)]
|
|
|
|
Config(ConfigCommand),
|
2022-08-20 04:01:43 +00:00
|
|
|
/// Commands for interacting with vaults.
|
|
|
|
#[clap(subcommand)]
|
|
|
|
Vault(VaultCommand),
|
2022-08-22 08:17:37 +00:00
|
|
|
/// Switches to the specified vault.
|
|
|
|
Switch {
|
|
|
|
name : String,
|
|
|
|
},
|
2022-08-20 04:01:43 +00:00
|
|
|
}
|
|
|
|
|
2022-08-25 10:44:10 +00:00
|
|
|
#[derive(clap::StructOpt, Debug, PartialEq, Eq)]
|
|
|
|
pub struct ListOptions {
|
2022-08-29 07:34:22 +00:00
|
|
|
/// Which columns to include.
|
|
|
|
#[clap(short, value_enum)]
|
|
|
|
column : Vec<Column>,
|
|
|
|
/// Field to order by.
|
|
|
|
#[clap(long, value_enum, default_value_t=OrderBy::Id)]
|
|
|
|
order_by : OrderBy,
|
2022-08-25 10:55:28 +00:00
|
|
|
/// Sort ascending on descending.
|
2022-08-29 07:34:22 +00:00
|
|
|
#[clap(long, value_enum, default_value_t=Order::Asc)]
|
|
|
|
order : Order,
|
|
|
|
/// Tags to include.
|
|
|
|
#[clap(short, long)]
|
|
|
|
tag : Vec<String>,
|
|
|
|
/// Only include tasks due before a certain date (inclusive).
|
2022-08-25 10:44:10 +00:00
|
|
|
#[clap(long)]
|
2022-08-29 07:34:22 +00:00
|
|
|
due_before : Option<chrono::NaiveDate>,
|
|
|
|
/// Only include tasks due after a certain date (inclusive).
|
2022-08-25 10:44:10 +00:00
|
|
|
#[clap(long)]
|
2022-08-29 07:34:22 +00:00
|
|
|
due_after : Option<chrono::NaiveDate>,
|
|
|
|
/// Only include tasks created before a certain date (inclusive).
|
2022-08-25 10:44:10 +00:00
|
|
|
#[clap(long)]
|
2022-08-29 07:34:22 +00:00
|
|
|
created_before : Option<chrono::NaiveDate>,
|
|
|
|
/// Only include tasks created after a certain date (inclusive).
|
|
|
|
#[clap(long)]
|
|
|
|
created_after : Option<chrono::NaiveDate>,
|
2022-08-25 10:55:28 +00:00
|
|
|
/// Include completed tasks in the list.
|
2022-08-25 10:44:10 +00:00
|
|
|
#[clap(long)]
|
2022-08-25 10:55:28 +00:00
|
|
|
include_completed : bool,
|
2022-08-29 07:34:22 +00:00
|
|
|
/// Only include notes with no dependencies [alias: bottom-level].
|
|
|
|
#[clap(long, alias="bottom-level")]
|
|
|
|
no_dependencies : bool,
|
|
|
|
/// Only include notes with no dependents [alias: top-level].
|
|
|
|
#[clap(long, alias="top-level")]
|
|
|
|
no_dependents : bool,
|
2022-08-25 10:44:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Clone, Debug, PartialEq, Eq, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
|
2022-08-29 07:34:22 +00:00
|
|
|
pub enum Order {
|
2022-08-25 10:44:10 +00:00
|
|
|
#[default]
|
|
|
|
Asc,
|
|
|
|
Desc,
|
|
|
|
}
|
|
|
|
|
2022-08-29 07:34:22 +00:00
|
|
|
#[derive(Default, Hash, Clone, Debug, PartialEq, Eq, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
|
|
|
|
pub enum Column {
|
|
|
|
#[default]
|
|
|
|
Due,
|
|
|
|
Priority,
|
|
|
|
Created,
|
|
|
|
Tags,
|
|
|
|
Status,
|
|
|
|
Tracked,
|
|
|
|
}
|
|
|
|
|
2022-08-25 10:44:10 +00:00
|
|
|
#[derive(Default, Clone, Debug, PartialEq, Eq, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
|
2022-08-29 07:34:22 +00:00
|
|
|
pub enum OrderBy {
|
2022-08-25 10:44:10 +00:00
|
|
|
#[default]
|
|
|
|
Id,
|
|
|
|
Name,
|
|
|
|
Due,
|
|
|
|
Priority,
|
|
|
|
Created,
|
2022-08-29 07:34:22 +00:00
|
|
|
Tracked,
|
2022-08-25 10:44:10 +00:00
|
|
|
}
|
2022-08-23 23:56:23 +00:00
|
|
|
|
|
|
|
#[derive(clap::Subcommand, Debug, PartialEq, Eq)]
|
|
|
|
enum StatsCommand {
|
2022-08-24 23:15:15 +00:00
|
|
|
/// View time tracked per tag recently.
|
2022-08-23 23:56:23 +00:00
|
|
|
Tracked {
|
|
|
|
#[clap(short, long, default_value_t=7)]
|
|
|
|
days : u16,
|
|
|
|
},
|
2022-08-24 23:15:15 +00:00
|
|
|
/// View recently completed tasks.
|
|
|
|
Completed {
|
|
|
|
#[clap(short, long, default_value_t=7)]
|
|
|
|
days : u16,
|
|
|
|
},
|
2022-08-23 23:56:23 +00:00
|
|
|
}
|
|
|
|
|
2022-08-22 08:51:54 +00:00
|
|
|
#[derive(clap::Subcommand, Debug, PartialEq, Eq)]
|
2022-08-21 07:34:44 +00:00
|
|
|
enum ConfigCommand {
|
|
|
|
/// For checking or changing default text editor command.
|
|
|
|
Editor {
|
|
|
|
/// Command to launch editor. Omit to view current editor.
|
|
|
|
editor : Option<String>,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 08:51:54 +00:00
|
|
|
#[derive(clap::Subcommand, Debug, PartialEq, Eq)]
|
2022-08-20 04:01:43 +00:00
|
|
|
enum VaultCommand {
|
|
|
|
/// Creates a new vault at the specified location of the given name.
|
|
|
|
New {
|
|
|
|
name : String,
|
|
|
|
path : path::PathBuf,
|
|
|
|
},
|
|
|
|
/// Disconnects the specified vault from toru, without altering the files.
|
|
|
|
Disconnect {
|
|
|
|
name : String,
|
|
|
|
},
|
|
|
|
/// Connects an existing fault to toru.
|
|
|
|
Connect {
|
|
|
|
name : String,
|
|
|
|
path : path::PathBuf,
|
|
|
|
},
|
|
|
|
/// Deletes the specified vault along with all of its data.
|
|
|
|
Delete {
|
|
|
|
name : String,
|
|
|
|
},
|
|
|
|
/// Lists all configured vaults.
|
|
|
|
List,
|
2022-08-23 11:05:28 +00:00
|
|
|
/// For renaming an already set up vault.
|
|
|
|
Rename {
|
|
|
|
old_name : String,
|
|
|
|
new_name : String,
|
|
|
|
}
|
2022-08-20 04:01:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let result = program();
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Ok(()) => (),
|
2022-08-25 06:40:56 +00:00
|
|
|
Err(err) => {
|
|
|
|
println!("{}", err);
|
2022-08-20 04:01:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn program() -> Result<(), error::Error> {
|
|
|
|
let command = {
|
|
|
|
use clap::Parser;
|
|
|
|
Args::parse().command
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut config = config::Config::load()?;
|
|
|
|
|
|
|
|
use Command::*;
|
2022-08-22 08:17:37 +00:00
|
|
|
if let Vault(command) = command {
|
|
|
|
use VaultCommand::*;
|
|
|
|
match command {
|
|
|
|
New { name, path } => {
|
|
|
|
vault::new(name.clone(), path, &mut config)?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Created vault {}", colour::text::vault(&name));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
Disconnect { name } => {
|
|
|
|
vault::disconnect(&name, &mut config)?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Disconnected vault {}", colour::text::vault(&name));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
Connect { name , path } => {
|
|
|
|
vault::connect(name.clone(), path, &mut config)?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Connected vault {}", colour::text::vault(&name));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
Delete { name } => {
|
|
|
|
vault::delete(&name, &mut config)?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Deleted vault {}", colour::text::vault(&name));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
List => {
|
|
|
|
config.list_vaults()?;
|
|
|
|
},
|
2022-08-23 11:05:28 +00:00
|
|
|
Rename { old_name, new_name } => {
|
|
|
|
config.rename_vault(&old_name, new_name.clone())?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Renamed vault {} to {}", colour::text::vault(&old_name), colour::text::vault(&new_name));
|
2022-08-23 11:05:28 +00:00
|
|
|
}
|
2022-08-22 08:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if let Config(command) = command {
|
|
|
|
use ConfigCommand::*;
|
|
|
|
match command {
|
|
|
|
Editor { editor } => {
|
|
|
|
match editor {
|
|
|
|
Some(editor) => {
|
|
|
|
config.editor = editor;
|
|
|
|
println!("Updated editor command to: {}", config.editor);
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
println!("Current editor command: {}", config.editor);
|
2022-08-21 07:34:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-22 08:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if let Switch { name } = command {
|
|
|
|
config.switch(&name)?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Switched to vault {}", colour::text::vault(&name));
|
2022-08-22 08:17:37 +00:00
|
|
|
}
|
|
|
|
else if let Git { args } = command {
|
|
|
|
let vault_folder = &config.current_vault()?.1;
|
2022-08-24 10:38:47 +00:00
|
|
|
vcs::command(args, vcs::Vcs::Git, vault_folder)?;
|
2022-08-22 08:17:37 +00:00
|
|
|
}
|
2022-08-22 08:51:54 +00:00
|
|
|
else if command == GitIgnore {
|
|
|
|
let vault_folder = &config.current_vault()?.1;
|
2022-08-24 10:38:47 +00:00
|
|
|
vcs::create_gitignore(vault_folder)?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Default {} file created", colour::text::file(".gitignore"));
|
2022-08-22 08:51:54 +00:00
|
|
|
}
|
2022-08-24 10:38:47 +00:00
|
|
|
else if let Svn { args } = command {
|
|
|
|
let vault_folder = &config.current_vault()?.1;
|
|
|
|
vcs::command(args, vcs::Vcs::Svn, vault_folder)?;
|
|
|
|
}
|
2022-08-22 08:17:37 +00:00
|
|
|
// Commands that require loading in the state.
|
|
|
|
else {
|
|
|
|
let vault_folder = &config.current_vault()?.1;
|
|
|
|
let mut state = state::State::load(vault_folder)?;
|
|
|
|
|
|
|
|
match command {
|
2022-08-24 12:09:52 +00:00
|
|
|
New { name, info, tags, dependencies, priority, due } => {
|
|
|
|
let task = tasks::Task::new(name, info, tags, dependencies, priority, due, vault_folder, &mut state)?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Created task {} (ID: {})", colour::text::task(&task.data.name), colour::text::id(task.data.id));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
Delete { id_or_name } => {
|
2022-08-25 00:44:22 +00:00
|
|
|
let id = state.data.index.lookup(&id_or_name)?;
|
2022-08-22 08:17:37 +00:00
|
|
|
let task = tasks::Task::load(id, vault_folder, false)?;
|
|
|
|
let name = task.data.name.clone();
|
2022-08-25 00:44:22 +00:00
|
|
|
state.data.index.remove(task.data.name.clone(), task.data.id);
|
2022-08-25 06:40:56 +00:00
|
|
|
state.data.deps.remove_node(task.data.id);
|
2022-08-22 08:17:37 +00:00
|
|
|
task.delete()?;
|
|
|
|
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Deleted task {} (ID: {})", colour::text::task(&name), colour::text::id(id));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
View { id_or_name } => {
|
2022-08-25 00:44:22 +00:00
|
|
|
let id = state.data.index.lookup(&id_or_name)?;
|
2022-08-22 08:17:37 +00:00
|
|
|
let task = tasks::Task::load(id, vault_folder, true)?;
|
2022-08-25 06:40:56 +00:00
|
|
|
task.display(vault_folder, &state)?;
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
Edit { id_or_name, info } => {
|
2022-08-25 00:44:22 +00:00
|
|
|
let id = state.data.index.lookup(&id_or_name)?;
|
2022-08-22 08:17:37 +00:00
|
|
|
if info {
|
|
|
|
edit::edit_info(id, vault_folder.clone(), &config.editor)?;
|
2022-08-20 23:58:05 +00:00
|
|
|
}
|
2022-08-22 08:17:37 +00:00
|
|
|
else {
|
|
|
|
edit::edit_raw(id, vault_folder.clone(), &config.editor, &mut state)?;
|
|
|
|
}
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Updated task {}", colour::text::id(id));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
2022-08-30 08:23:23 +00:00
|
|
|
Track { id_or_name, hours, minutes, date, message } => {
|
2022-08-25 00:44:22 +00:00
|
|
|
let id = state.data.index.lookup(&id_or_name)?;
|
2022-08-22 10:27:17 +00:00
|
|
|
let mut task = tasks::Task::load(id, vault_folder, false)?;
|
2022-08-30 08:23:23 +00:00
|
|
|
let entry = tasks::TimeEntry::new(hours, minutes, date, message);
|
2022-08-22 10:27:17 +00:00
|
|
|
task.data.time_entries.push(entry);
|
|
|
|
task.save()?;
|
|
|
|
},
|
2022-08-23 23:56:23 +00:00
|
|
|
Stats(command) => {
|
|
|
|
use StatsCommand::*;
|
|
|
|
match command {
|
|
|
|
Tracked { days } => {
|
|
|
|
stats::time_per_tag(days, vault_folder)?;
|
2022-08-24 23:15:15 +00:00
|
|
|
},
|
|
|
|
Completed { days } => {
|
|
|
|
stats::completed_tasks(days, vault_folder)?;
|
2022-08-23 23:56:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2022-08-22 08:17:37 +00:00
|
|
|
Complete { id_or_name } => {
|
2022-08-25 00:44:22 +00:00
|
|
|
let id = state.data.index.lookup(&id_or_name)?;
|
2022-08-22 08:17:37 +00:00
|
|
|
let mut task = tasks::Task::load(id, vault_folder, false)?;
|
2022-08-24 22:30:52 +00:00
|
|
|
task.data.completed = Some(chrono::Local::now().naive_local());
|
2022-08-22 08:17:37 +00:00
|
|
|
task.save()?;
|
2022-08-29 09:38:21 +00:00
|
|
|
println!("Marked task {} as complete", colour::text::id(id));
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
2022-08-25 10:44:10 +00:00
|
|
|
List { options } => {
|
2022-08-29 07:34:22 +00:00
|
|
|
tasks::list(options, vault_folder, &state)?;
|
2022-08-22 08:17:37 +00:00
|
|
|
},
|
|
|
|
// All commands which are dealt with in if let chain at start.
|
2022-08-24 10:38:47 +00:00
|
|
|
Vault(_) | Config(_) | Git { args : _ } | Svn { args : _ } | Switch { name : _ } | GitIgnore => unreachable!(),
|
2022-08-20 04:01:43 +00:00
|
|
|
}
|
2022-08-22 08:17:37 +00:00
|
|
|
|
|
|
|
state.save()?;
|
2022-08-20 04:01:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
config.save()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|