🔧 Code improvements
This commit is contained in:
parent
c08af0172c
commit
ae757fbe74
97
Cargo.lock
generated
97
Cargo.lock
generated
@ -2,20 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "agenda"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"clap",
|
|
||||||
"colored",
|
|
||||||
"dirs",
|
|
||||||
"fuzzydate",
|
|
||||||
"prettytable-rs",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@ -301,6 +287,12 @@ dependencies = [
|
|||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -346,6 +338,31 @@ dependencies = [
|
|||||||
"cxx-build",
|
"cxx-build",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inertia"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"clap",
|
||||||
|
"colored",
|
||||||
|
"dirs",
|
||||||
|
"fuzzydate",
|
||||||
|
"prettytable-rs",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-lifetimes"
|
name = "io-lifetimes"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@ -595,6 +612,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -663,6 +689,40 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.19.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@ -843,3 +903,12 @@ name = "windows_x86_64_msvc"
|
|||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c06e7dbfe731192c512aa707249279d720876a868eb08f21ea93eb9de1eca9"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "agenda"
|
name = "inertia"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@ -15,3 +15,4 @@ dirs = "4.0.0"
|
|||||||
colored = "2.0.0"
|
colored = "2.0.0"
|
||||||
prettytable-rs = "0.10.0"
|
prettytable-rs = "0.10.0"
|
||||||
fuzzydate = "0.2.1"
|
fuzzydate = "0.2.1"
|
||||||
|
toml = "0.7.2"
|
||||||
|
27
src/args.rs
27
src/args.rs
@ -7,7 +7,7 @@ pub struct TasksArgs {
|
|||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, PartialEq, Debug)]
|
#[derive(Subcommand, PartialEq, Eq, Debug)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// Creates a new task
|
/// Creates a new task
|
||||||
Add(CreateTask),
|
Add(CreateTask),
|
||||||
@ -25,7 +25,7 @@ pub enum Commands {
|
|||||||
Stop(StopTask),
|
Stop(StopTask),
|
||||||
/// Edit a task with $EDITOR
|
/// Edit a task with $EDITOR
|
||||||
Edit(EditTask),
|
Edit(EditTask),
|
||||||
/// Modify a task at the cli
|
/// Modify a task at the command line
|
||||||
Modify(ModifyTask),
|
Modify(ModifyTask),
|
||||||
/// Passes git commands to the repository
|
/// Passes git commands to the repository
|
||||||
Git(GitExecute),
|
Git(GitExecute),
|
||||||
@ -35,7 +35,7 @@ pub enum Commands {
|
|||||||
Undo(UndoExecute),
|
Undo(UndoExecute),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct CreateTask {
|
pub struct CreateTask {
|
||||||
/// Title of the task
|
/// Title of the task
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@ -65,44 +65,43 @@ pub struct CreateTask {
|
|||||||
#[clap(default_value=None)]
|
#[clap(default_value=None)]
|
||||||
pub reminder: Option<String>,
|
pub reminder: Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct DeleteTask {
|
pub struct DeleteTask {
|
||||||
/// ID of the task
|
/// ID of the task
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct ShowTask {
|
pub struct ShowTask {
|
||||||
/// ID of the task
|
/// ID of the task
|
||||||
#[clap(default_value=None)]
|
#[clap(default_value=None)]
|
||||||
pub id: Option<usize>,
|
pub id: Option<usize>,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct CompleteTask {
|
pub struct CompleteTask {
|
||||||
/// ID of the task
|
/// ID of the task
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct StartTask {
|
pub struct StartTask {
|
||||||
/// ID of the task
|
/// ID of the task
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct StopTask {
|
pub struct StopTask {
|
||||||
/// ID of the task
|
/// ID of the task
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct EditTask {
|
pub struct EditTask {
|
||||||
/// ID of the task
|
/// ID of the task
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct ModifyTask {
|
pub struct ModifyTask {
|
||||||
/// ID of the task
|
/// ID of the task
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
|
|
||||||
/// Title of the task
|
/// Title of the task
|
||||||
#[arg(short, long)]
|
|
||||||
#[clap(default_value=None)]
|
#[clap(default_value=None)]
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
|
||||||
@ -131,18 +130,18 @@ pub struct ModifyTask {
|
|||||||
#[clap(default_value=None)]
|
#[clap(default_value=None)]
|
||||||
pub reminder: Option<String>,
|
pub reminder: Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
pub struct GitExecute {
|
pub struct GitExecute {
|
||||||
/// Git command to run
|
/// Git command to run
|
||||||
pub command: String,
|
pub command: String,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Debug)]
|
#[derive(Args, PartialEq, Eq, 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, Eq, 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")]
|
||||||
|
217
src/cli.rs
217
src/cli.rs
@ -1,86 +1,14 @@
|
|||||||
|
mod dates;
|
||||||
|
pub mod output;
|
||||||
|
mod tables;
|
||||||
|
|
||||||
|
mod cmds;
|
||||||
|
|
||||||
use crate::args::{Commands, TasksArgs};
|
use crate::args::{Commands, TasksArgs};
|
||||||
use crate::args::{CompleteTask, CreateTask, DeleteTask, ShowTask, StartTask, StopTask, ModifyTask};
|
use crate::args::{
|
||||||
use crate::tasks::{Status, Task, Tasks, TasksError};
|
CompleteTask, CreateTask, DeleteTask, ModifyTask, ShowTask, StartTask, StopTask,
|
||||||
use chrono::{Local, NaiveDateTime};
|
};
|
||||||
use colored::*;
|
use crate::tasks::{Tasks, TasksError};
|
||||||
use fuzzydate;
|
|
||||||
use prettytable::{format, row, Row, Table};
|
|
||||||
use std::panic;
|
|
||||||
|
|
||||||
pub fn warning(msg: &str) {
|
|
||||||
println!("{} {}", "warning:".yellow().bold(), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info(msg: String) {
|
|
||||||
println!("{} {}", "info:".blue().bold(), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn success(msg: String) {
|
|
||||||
println!("{} {}", "success:".green().bold(), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn task_msg(msg: &str, task: &Task, id: usize) -> String {
|
|
||||||
format!(
|
|
||||||
"{} task: {}({})",
|
|
||||||
msg,
|
|
||||||
task.title.blue(),
|
|
||||||
id.to_string().cyan()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_date(date_string: Option<String>) -> Option<NaiveDateTime> {
|
|
||||||
if date_string.is_some() {
|
|
||||||
match fuzzydate::parse(date_string.unwrap()) {
|
|
||||||
Ok(date) => Some(date),
|
|
||||||
Err(err) => panic!("{} {:?}", "error:".red().bold(), err),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn date_to_string(date: &Option<NaiveDateTime>) -> ColoredString {
|
|
||||||
if date.is_some() {
|
|
||||||
let date = date.unwrap().date();
|
|
||||||
let date_string = format!("{}", date.format("%Y-%m-%d"));
|
|
||||||
let now = Local::now().date_naive();
|
|
||||||
|
|
||||||
if date <= now {
|
|
||||||
// If the date is today or past today
|
|
||||||
date_string.bright_red()
|
|
||||||
} else if now.succ_opt().unwrap() == date {
|
|
||||||
// If the date is tomorrow
|
|
||||||
date_string.yellow()
|
|
||||||
} else {
|
|
||||||
// Otherwise the date is too far in the past
|
|
||||||
date_string.white()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"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
|
|
||||||
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 {
|
||||||
@ -91,133 +19,48 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<&mut Tasks, Ta
|
|||||||
when,
|
when,
|
||||||
deadline,
|
deadline,
|
||||||
reminder,
|
reminder,
|
||||||
..
|
|
||||||
}) => {
|
}) => {
|
||||||
let when = parse_date(when);
|
cmds::add(tasks, title, notes, tags, when, deadline, reminder);
|
||||||
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 = Task::new(title, notes, tags, when, deadline, reminder);
|
|
||||||
tasks.push(task.clone());
|
|
||||||
|
|
||||||
let id = tasks.len() - 1;
|
|
||||||
|
|
||||||
success(task_msg("created", &task, id));
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Modify(ModifyTask { id, title, notes, tags, when, deadline, reminder }) => {
|
Commands::Modify(ModifyTask {
|
||||||
let when = parse_date(when);
|
id,
|
||||||
let deadline = parse_date(deadline);
|
title,
|
||||||
let reminder = parse_date(reminder);
|
notes,
|
||||||
let tags: Option<Vec<String>> = if tags.is_some() {
|
tags,
|
||||||
Some(tags.unwrap().split(",").map(str::to_string).collect())
|
when,
|
||||||
} else {
|
deadline,
|
||||||
None
|
reminder,
|
||||||
};
|
}) => {
|
||||||
|
cmds::modify(tasks, id, title, notes, tags, when, deadline, reminder)?;
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Del(DeleteTask { id }) => {
|
Commands::Del(DeleteTask { id }) => {
|
||||||
let mut binding = tasks.clone();
|
cmds::delete(tasks, id)?;
|
||||||
let task = binding.get_task(id)?;
|
|
||||||
tasks.remove(id)?;
|
|
||||||
|
|
||||||
success(task_msg("deleted", &task, id));
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Done(CompleteTask { id }) => {
|
Commands::Done(CompleteTask { id }) => {
|
||||||
let task = tasks.get_task(id)?;
|
cmds::done(tasks, id)?;
|
||||||
task.complete();
|
|
||||||
|
|
||||||
success(task_msg("completed", &task, id));
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Start(StartTask { id }) => {
|
Commands::Start(StartTask { id }) => {
|
||||||
let task = tasks.get_task(id)?;
|
cmds::start(tasks, id)?;
|
||||||
task.start();
|
|
||||||
|
|
||||||
success(task_msg("started", &task, id));
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Stop(StopTask { id }) => {
|
Commands::Stop(StopTask { id }) => {
|
||||||
let task = tasks.get_task(id)?;
|
cmds::stop(tasks, id)?;
|
||||||
task.stop();
|
|
||||||
|
|
||||||
success(task_msg("stopped", &task, id));
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Clear => {
|
Commands::Clear => {
|
||||||
tasks.clear()?;
|
cmds::clear(tasks)?;
|
||||||
|
|
||||||
success(String::from("cleared all tasks"));
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Show(ShowTask { id }) => {
|
Commands::Show(ShowTask { id }) => {
|
||||||
if id.is_none() {
|
cmds::show(tasks, id)?;
|
||||||
if tasks.is_empty() {
|
|
||||||
info(String::from("no tasks found"))
|
|
||||||
} else {
|
|
||||||
// Create the table for printing
|
|
||||||
let mut table = Table::new();
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Iterate through each task
|
|
||||||
let mut id = 0;
|
|
||||||
for task in tasks.tasks.as_ref().unwrap() {
|
|
||||||
table.add_row(calc_row(task, id));
|
|
||||||
id += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the table
|
|
||||||
println!("{}", table);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Get the task
|
|
||||||
let id = id.unwrap();
|
|
||||||
let task = tasks.get_task(id)?;
|
|
||||||
|
|
||||||
// Generate and print the table
|
|
||||||
let mut table = Table::new();
|
|
||||||
table.set_titles(row![
|
|
||||||
"Item".magenta().bold(),
|
|
||||||
"Value".magenta().bold(),
|
|
||||||
]);
|
|
||||||
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
|
||||||
table.add_row(calc_row(&task, id));
|
|
||||||
println!("{}", table)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
|
153
src/cli/cmds.rs
Normal file
153
src/cli/cmds.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use crate::cli::dates;
|
||||||
|
use crate::cli::output;
|
||||||
|
use crate::cli::tables;
|
||||||
|
use crate::tasks::{Task, Tasks, TasksError};
|
||||||
|
|
||||||
|
fn parse_tags(tags: Option<String>) -> Option<Vec<String>> {
|
||||||
|
if let Some(..) = tags {
|
||||||
|
Some(tags.unwrap().split(',').map(str::to_string).collect())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(tasks: &mut Tasks, id: Option<usize>) -> Result<(), TasksError> {
|
||||||
|
// If no id is given, print out all tasks
|
||||||
|
if let Some(..) = id {
|
||||||
|
if tasks.is_empty() {
|
||||||
|
// Output when no tasks are available
|
||||||
|
output::info(String::from("no tasks found"))
|
||||||
|
} else {
|
||||||
|
// Generate the table of all tasks
|
||||||
|
let table = tables::tasks_table(tasks);
|
||||||
|
// Print the table
|
||||||
|
println!("{}", table);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Get the task the user wants to see
|
||||||
|
let id = id.unwrap();
|
||||||
|
let task = tasks.get_task(id)?;
|
||||||
|
|
||||||
|
// Generate the table for the singular task
|
||||||
|
let table = tables::task_table(task, id);
|
||||||
|
// Print the table
|
||||||
|
println!("{}", table);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Success
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(
|
||||||
|
tasks: &mut Tasks,
|
||||||
|
title: String,
|
||||||
|
notes: Option<String>,
|
||||||
|
tags: Option<String>,
|
||||||
|
when: Option<String>,
|
||||||
|
deadline: Option<String>,
|
||||||
|
reminder: Option<String>,
|
||||||
|
) {
|
||||||
|
// Parse dates and tags
|
||||||
|
let when = dates::parse_fuzzy_date(when);
|
||||||
|
let deadline = dates::parse_fuzzy_date(deadline);
|
||||||
|
let reminder = dates::parse_fuzzy_date(reminder);
|
||||||
|
let tags = parse_tags(tags);
|
||||||
|
|
||||||
|
// Generate a new task
|
||||||
|
let task = Task::new(title, notes, tags, when, deadline, reminder);
|
||||||
|
// Add the task to the tasks
|
||||||
|
tasks.push(task.clone());
|
||||||
|
|
||||||
|
// Calculate the id for output
|
||||||
|
let id = tasks.len() - 1;
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(output::task_msg("created", &task, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modify(
|
||||||
|
tasks: &mut Tasks,
|
||||||
|
id: usize,
|
||||||
|
title: Option<String>,
|
||||||
|
notes: Option<String>,
|
||||||
|
tags: Option<String>,
|
||||||
|
when: Option<String>,
|
||||||
|
deadline: Option<String>,
|
||||||
|
reminder: Option<String>,
|
||||||
|
) -> Result<(), TasksError> {
|
||||||
|
// Parse dates and tags
|
||||||
|
let when = dates::parse_fuzzy_date(when);
|
||||||
|
let deadline = dates::parse_fuzzy_date(deadline);
|
||||||
|
let reminder = dates::parse_fuzzy_date(reminder);
|
||||||
|
let tags = parse_tags(tags);
|
||||||
|
|
||||||
|
// Get the task the user wants
|
||||||
|
let task = tasks.get_task(id)?;
|
||||||
|
|
||||||
|
// If the the user changes the title, show that here
|
||||||
|
if title.is_some() {
|
||||||
|
output::info(output::task_msg("renaming task", task, id))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modify the task
|
||||||
|
task.modify(title, notes, tags, when, deadline, reminder);
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(output::task_msg("modified", task, id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
|
// Get the task the user wants to delete for output later
|
||||||
|
let mut binding = tasks.clone();
|
||||||
|
let task = binding.get_task(id)?;
|
||||||
|
|
||||||
|
// Delete the task
|
||||||
|
tasks.remove(id)?;
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(output::task_msg("deleted", task, id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(tasks: &mut Tasks) -> Result<(), TasksError> {
|
||||||
|
// Clear all tasks
|
||||||
|
tasks.clear()?;
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(String::from("cleared all tasks"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
|
// Get the task the user wants to stop
|
||||||
|
let task = tasks.get_task(id)?;
|
||||||
|
// Stop the task
|
||||||
|
task.stop();
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(output::task_msg("stopped", task, id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
|
// Get the task the user wants to start
|
||||||
|
let task = tasks.get_task(id)?;
|
||||||
|
// Start the task
|
||||||
|
task.start();
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(output::task_msg("started", task, id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn done(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
|
// Get the task the user wants to complete
|
||||||
|
let task = tasks.get_task(id)?;
|
||||||
|
// Complete the task
|
||||||
|
task.complete();
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(output::task_msg("completed", task, id));
|
||||||
|
Ok(())
|
||||||
|
}
|
34
src/cli/dates.rs
Normal file
34
src/cli/dates.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use chrono::{Local, NaiveDateTime};
|
||||||
|
use colored::{ColoredString, Colorize};
|
||||||
|
|
||||||
|
pub fn parse_fuzzy_date(date_string: Option<String>) -> Option<NaiveDateTime> {
|
||||||
|
if let Some(..) = date_string {
|
||||||
|
match fuzzydate::parse(date_string.unwrap()) {
|
||||||
|
Ok(date) => Some(date),
|
||||||
|
Err(err) => panic!("{} {:?}", "error:".red().bold(), err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn date_as_string(date: &Option<NaiveDateTime>) -> ColoredString {
|
||||||
|
if date.is_some() {
|
||||||
|
let date = date.unwrap().date();
|
||||||
|
let date_string = format!("{}", date.format("%Y-%m-%d"));
|
||||||
|
let now = Local::now().date_naive();
|
||||||
|
|
||||||
|
if date <= now {
|
||||||
|
// If the date is today or past today
|
||||||
|
date_string.bright_red()
|
||||||
|
} else if now.succ_opt().unwrap() == date {
|
||||||
|
// If the date is tomorrow
|
||||||
|
date_string.yellow()
|
||||||
|
} else {
|
||||||
|
// Otherwise the date is too far in the past
|
||||||
|
date_string.white()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"N/A".bright_black()
|
||||||
|
}
|
||||||
|
}
|
23
src/cli/output.rs
Normal file
23
src/cli/output.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use crate::tasks::Task;
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
pub fn warning(msg: &str) {
|
||||||
|
println!("{} {}", "warning:".yellow().bold(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info(msg: String) {
|
||||||
|
println!("{} {}", "info:".blue().bold(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn success(msg: String) {
|
||||||
|
println!("{} {}", "success:".green().bold(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn task_msg(msg: &str, task: &Task, id: usize) -> String {
|
||||||
|
format!(
|
||||||
|
"{} task: {}({})",
|
||||||
|
msg,
|
||||||
|
task.title.blue(),
|
||||||
|
id.to_string().cyan()
|
||||||
|
)
|
||||||
|
}
|
58
src/cli/tables.rs
Normal file
58
src/cli/tables.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use colored::Colorize;
|
||||||
|
use prettytable::{format, row, Row, Table};
|
||||||
|
|
||||||
|
use crate::cli::dates;
|
||||||
|
use crate::tasks::{Status, Task, Tasks};
|
||||||
|
|
||||||
|
pub 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(),
|
||||||
|
dates::date_as_string(&task.when).bright_black().italic(),
|
||||||
|
dates::date_as_string(&task.deadline)
|
||||||
|
.bright_black()
|
||||||
|
.italic(),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
// Generate normal colored rows for uncompleted tasks
|
||||||
|
Row::from([
|
||||||
|
id.to_string().cyan(),
|
||||||
|
task.status.as_string(),
|
||||||
|
task.title.clone().white(),
|
||||||
|
dates::date_as_string(&task.when),
|
||||||
|
dates::date_as_string(&task.deadline),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tasks_table(tasks: &Tasks) -> Table {
|
||||||
|
// Create the table for printing
|
||||||
|
let mut table = Table::new();
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Iterate through each task
|
||||||
|
for (id, task) in tasks.tasks.as_ref().unwrap().iter().enumerate() {
|
||||||
|
table.add_row(calc_row(task, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn task_table(task: &Task, id: usize) -> Table {
|
||||||
|
let mut table = Table::new();
|
||||||
|
table.set_titles(row!["Item".magenta().bold(), "Value".magenta().bold()]);
|
||||||
|
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
||||||
|
table.add_row(calc_row(task, id));
|
||||||
|
|
||||||
|
table
|
||||||
|
}
|
10
src/data.rs
10
src/data.rs
@ -1,5 +1,4 @@
|
|||||||
use dirs::home_dir;
|
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;
|
||||||
@ -9,8 +8,8 @@ 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>> {
|
||||||
// Convert the tasks to JSON form
|
// Convert the tasks to TOML format
|
||||||
let data = serde_json::to_string_pretty(&tasks)?;
|
let data = toml::to_string_pretty(&tasks)?;
|
||||||
|
|
||||||
// Write the JSON to the file
|
// Write the JSON to the file
|
||||||
fs::write(path, data)?;
|
fs::write(path, data)?;
|
||||||
@ -22,13 +21,14 @@ pub fn load_tasks<P: AsRef<Path>>(path: P) -> Result<Tasks, Box<dyn Error>> {
|
|||||||
// Read JSON from the file
|
// Read JSON from the file
|
||||||
let data = fs::read_to_string(path)?;
|
let data = fs::read_to_string(path)?;
|
||||||
|
|
||||||
// Load the tasks from JSON form
|
// Load the tasks from TOML form
|
||||||
let tasks: Tasks = serde_json::from_str(&data)?;
|
let tasks: Tasks = toml::from_str(&data)?;
|
||||||
|
|
||||||
Ok(tasks)
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tasks_file_path() -> String {
|
pub fn tasks_file_path() -> String {
|
||||||
|
// Generate the path for the location of tasks
|
||||||
format!(
|
format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
home_dir().unwrap().to_str().unwrap(),
|
home_dir().unwrap().to_str().unwrap(),
|
||||||
|
@ -15,7 +15,7 @@ fn main() {
|
|||||||
|
|
||||||
// If the tasks file doesn't exist, create it first
|
// If the tasks file doesn't exist, create it first
|
||||||
if !Path::new(&tasks_file_path).exists() {
|
if !Path::new(&tasks_file_path).exists() {
|
||||||
cli::warning("file '~/.local/share/tasks' does not exist. creating..");
|
cli::output::warning("file '~/.local/share/tasks' does not exist. creating...");
|
||||||
let tasks = Tasks::new(&tasks_file_path);
|
let tasks = Tasks::new(&tasks_file_path);
|
||||||
data::save_tasks(&tasks_file_path, &tasks).unwrap();
|
data::save_tasks(&tasks_file_path, &tasks).unwrap();
|
||||||
};
|
};
|
||||||
@ -34,5 +34,5 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Save any changes
|
// Save any changes
|
||||||
data::save_tasks(tasks_file_path, &tasks).unwrap()
|
data::save_tasks(tasks_file_path, tasks).unwrap()
|
||||||
}
|
}
|
||||||
|
40
src/tasks.rs
40
src/tasks.rs
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TasksError(String);
|
pub struct TasksError(String);
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Inbox,
|
Inbox,
|
||||||
Pending,
|
Pending,
|
||||||
@ -64,28 +64,36 @@ impl Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn modify(&mut self, title: Option<String>, notes: Option<String>, tags: Option<Vec<String>>, when: Option<NaiveDateTime>, deadline: Option<NaiveDateTime>, reminder: Option<NaiveDateTime>) {
|
pub fn modify(
|
||||||
if title.is_some() {
|
&mut self,
|
||||||
|
title: Option<String>,
|
||||||
|
notes: Option<String>,
|
||||||
|
tags: Option<Vec<String>>,
|
||||||
|
when: Option<NaiveDateTime>,
|
||||||
|
deadline: Option<NaiveDateTime>,
|
||||||
|
reminder: Option<NaiveDateTime>,
|
||||||
|
) {
|
||||||
|
if let Some(..) = title {
|
||||||
self.title = title.unwrap();
|
self.title = title.unwrap();
|
||||||
};
|
};
|
||||||
|
|
||||||
if notes.is_some() {
|
if let Some(..) = notes {
|
||||||
self.notes = Some(notes.unwrap());
|
self.notes = Some(notes.unwrap());
|
||||||
};
|
};
|
||||||
|
|
||||||
if tags.is_some() {
|
if let Some(..) = tags {
|
||||||
self.tags = Some(tags.unwrap());
|
self.tags = Some(tags.unwrap());
|
||||||
};
|
};
|
||||||
|
|
||||||
if when.is_some() {
|
if let Some(..) = when {
|
||||||
self.when = Some(when.unwrap());
|
self.when = Some(when.unwrap());
|
||||||
};
|
};
|
||||||
|
|
||||||
if deadline.is_some() {
|
if let Some(..) = deadline {
|
||||||
self.deadline = Some(deadline.unwrap());
|
self.deadline = Some(deadline.unwrap());
|
||||||
};
|
};
|
||||||
|
|
||||||
if reminder.is_some() {
|
if let Some(..) = reminder {
|
||||||
self.reminder = Some(reminder.unwrap());
|
self.reminder = Some(reminder.unwrap());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -116,32 +124,22 @@ impl Tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn task_exists(&self, id: usize) -> bool {
|
pub fn task_exists(&self, id: usize) -> bool {
|
||||||
if id >= self.len() {
|
id < self.len()
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
if self.len() == 0 {
|
self.len() == 0
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(no_tasks_available())
|
Err(no_tasks_available())
|
||||||
} else {
|
} else if self.task_exists(id) {
|
||||||
if self.task_exists(id) {
|
|
||||||
Ok(&mut self.tasks.as_mut().unwrap()[id])
|
Ok(&mut self.tasks.as_mut().unwrap()[id])
|
||||||
} else {
|
} else {
|
||||||
Err(task_not_found(id))
|
Err(task_not_found(id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, task: Task) {
|
pub fn push(&mut self, task: Task) {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
|
Loading…
Reference in New Issue
Block a user