🎨 General code improvements and fixes
This commit is contained in:
parent
b53ce1de5f
commit
81d6fd91e5
@ -23,6 +23,8 @@ pub enum Commands {
|
|||||||
Start(StartTask),
|
Start(StartTask),
|
||||||
/// Marks a task as pending
|
/// Marks a task as pending
|
||||||
Stop(StopTask),
|
Stop(StopTask),
|
||||||
|
/// Returns a task to the inbox
|
||||||
|
Inbox(InboxTask),
|
||||||
/// Edit a task with $EDITOR
|
/// Edit a task with $EDITOR
|
||||||
Edit(EditTask),
|
Edit(EditTask),
|
||||||
/// Modify a task at the command line
|
/// Modify a task at the command line
|
||||||
@ -92,6 +94,11 @@ pub struct StopTask {
|
|||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
#[derive(Args, PartialEq, Eq, Debug)]
|
#[derive(Args, PartialEq, Eq, Debug)]
|
||||||
|
pub struct InboxTask {
|
||||||
|
/// ID of the task
|
||||||
|
pub id: usize,
|
||||||
|
}
|
||||||
|
#[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,
|
||||||
|
12
src/cli.rs
12
src/cli.rs
@ -6,8 +6,10 @@ mod tables;
|
|||||||
|
|
||||||
use crate::args::{Commands, GitExecute, TasksArgs};
|
use crate::args::{Commands, GitExecute, TasksArgs};
|
||||||
use crate::args::{
|
use crate::args::{
|
||||||
CompleteTask, CreateTask, DeleteTask, ModifyTask, ShowTask, StartTask, StopTask, SyncTasks,
|
CompleteTask, CreateTask, DeleteTask, InboxTask, ModifyTask, ShowTask, StartTask, StopTask,
|
||||||
|
SyncTasks,
|
||||||
};
|
};
|
||||||
|
use crate::repo;
|
||||||
use crate::tasks::{Tasks, TasksError};
|
use crate::tasks::{Tasks, TasksError};
|
||||||
|
|
||||||
pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<(), TasksError> {
|
pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<(), TasksError> {
|
||||||
@ -51,6 +53,10 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<(), TasksError
|
|||||||
cmds::stop(tasks, id)?;
|
cmds::stop(tasks, id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Commands::Inbox(InboxTask { id }) => {
|
||||||
|
cmds::inbox(tasks, id)?;
|
||||||
|
}
|
||||||
|
|
||||||
Commands::Clear => {
|
Commands::Clear => {
|
||||||
cmds::clear(tasks)?;
|
cmds::clear(tasks)?;
|
||||||
}
|
}
|
||||||
@ -59,12 +65,12 @@ pub fn execute(tasks: &mut Tasks, arguments: TasksArgs) -> Result<(), TasksError
|
|||||||
cmds::show(tasks, id)?;
|
cmds::show(tasks, id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Git(GitExecute { command }) => match git::execute(&tasks.path, command) {
|
Commands::Git(GitExecute { command }) => match repo::execute(&tasks.path, command) {
|
||||||
Ok(..) => (),
|
Ok(..) => (),
|
||||||
Err(..) => panic!("failed to execute git cmd"),
|
Err(..) => panic!("failed to execute git cmd"),
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands::Sync(SyncTasks { remote }) => match git::sync(&tasks.path, remote) {
|
Commands::Sync(SyncTasks { remote }) => match repo::sync(&tasks.path, remote) {
|
||||||
Ok(..) => (),
|
Ok(..) => (),
|
||||||
Err(..) => panic!("failed"),
|
Err(..) => panic!("failed"),
|
||||||
},
|
},
|
||||||
|
@ -4,6 +4,7 @@ use crate::cli::tables;
|
|||||||
use crate::tasks::{Task, Tasks, TasksError};
|
use crate::tasks::{Task, Tasks, TasksError};
|
||||||
|
|
||||||
fn parse_tags(tags: Option<String>) -> Option<Vec<String>> {
|
fn parse_tags(tags: Option<String>) -> Option<Vec<String>> {
|
||||||
|
// Split tags into a vector by commas
|
||||||
tags.map(|tags| tags.split(',').map(str::to_string).collect())
|
tags.map(|tags| tags.split(',').map(str::to_string).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ pub fn show(tasks: &mut Tasks, id: Option<usize>) -> Result<(), TasksError> {
|
|||||||
// If no id is given, print out all tasks
|
// If no id is given, print out all tasks
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
// Get the task the user wants to see
|
// Get the task the user wants to see
|
||||||
let task = tasks.get_task(id)?;
|
let task = tasks.task(id)?;
|
||||||
|
|
||||||
// Generate the table for the singular task
|
// Generate the table for the singular task
|
||||||
let table = tables::task_table(task, id);
|
let table = tables::task_table(task, id);
|
||||||
@ -75,7 +76,7 @@ pub fn modify(
|
|||||||
let tags = parse_tags(tags);
|
let tags = parse_tags(tags);
|
||||||
|
|
||||||
// Get the task the user wants
|
// Get the task the user wants
|
||||||
let task = tasks.get_task(id)?;
|
let task = tasks.task(id)?;
|
||||||
|
|
||||||
// If the the user changes the title, show that here
|
// If the the user changes the title, show that here
|
||||||
if title.is_some() {
|
if title.is_some() {
|
||||||
@ -93,7 +94,7 @@ pub fn modify(
|
|||||||
pub fn delete(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
pub fn delete(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
// Get the task the user wants to delete for output later
|
// Get the task the user wants to delete for output later
|
||||||
let mut binding = tasks.clone();
|
let mut binding = tasks.clone();
|
||||||
let task = binding.get_task(id)?;
|
let task = binding.task(id)?;
|
||||||
|
|
||||||
// Delete the task
|
// Delete the task
|
||||||
tasks.remove(id)?;
|
tasks.remove(id)?;
|
||||||
@ -114,7 +115,7 @@ pub fn clear(tasks: &mut Tasks) -> Result<(), TasksError> {
|
|||||||
|
|
||||||
pub fn stop(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
pub fn stop(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
// Get the task the user wants to stop
|
// Get the task the user wants to stop
|
||||||
let task = tasks.get_task(id)?;
|
let task = tasks.task(id)?;
|
||||||
// Stop the task
|
// Stop the task
|
||||||
task.stop();
|
task.stop();
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ pub fn stop(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
|||||||
|
|
||||||
pub fn start(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
pub fn start(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
// Get the task the user wants to start
|
// Get the task the user wants to start
|
||||||
let task = tasks.get_task(id)?;
|
let task = tasks.task(id)?;
|
||||||
// Start the task
|
// Start the task
|
||||||
task.start();
|
task.start();
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ pub fn start(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
|||||||
|
|
||||||
pub fn done(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
pub fn done(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
// Get the task the user wants to complete
|
// Get the task the user wants to complete
|
||||||
let task = tasks.get_task(id)?;
|
let task = tasks.task(id)?;
|
||||||
// Complete the task
|
// Complete the task
|
||||||
task.complete();
|
task.complete();
|
||||||
|
|
||||||
@ -144,3 +145,14 @@ pub fn done(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
|||||||
output::success(output::task_msg("completed", task, id));
|
output::success(output::task_msg("completed", task, id));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inbox(tasks: &mut Tasks, id: usize) -> Result<(), TasksError> {
|
||||||
|
// Get the task the user wants to return to the inbox
|
||||||
|
let task = tasks.task(id)?;
|
||||||
|
// Inbox the task
|
||||||
|
task.inbox();
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(output::task_msg("inboxed", task, id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use chrono::{Local, NaiveDateTime};
|
use chrono::NaiveDateTime;
|
||||||
use colored::{ColoredString, Colorize};
|
use colored::Colorize;
|
||||||
|
|
||||||
pub fn parse_fuzzy_date(date_string: Option<String>) -> Option<NaiveDateTime> {
|
pub fn parse_fuzzy_date(date_string: Option<String>) -> Option<NaiveDateTime> {
|
||||||
if let Some(date_string) = date_string {
|
if let Some(date_string) = date_string {
|
||||||
@ -11,24 +11,3 @@ pub fn parse_fuzzy_date(date_string: Option<String>) -> Option<NaiveDateTime> {
|
|||||||
None
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,30 +1 @@
|
|||||||
use std::error::Error;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use crate::cli::output;
|
|
||||||
|
|
||||||
pub fn execute(path: &str, command: String) -> Result<(), Box<dyn Error>> {
|
|
||||||
let output = Command::new("git")
|
|
||||||
.args(["-C", path])
|
|
||||||
.args(command.split(' '))
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.stdout.is_empty() {
|
|
||||||
output::git(String::from_utf8(output.stdout).unwrap());
|
|
||||||
};
|
|
||||||
if !output.stderr.is_empty() {
|
|
||||||
output::error(String::from_utf8(output.stderr).unwrap());
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync(repo_path: &str, remote: String) -> Result<(), Box<dyn Error>> {
|
|
||||||
execute(
|
|
||||||
repo_path,
|
|
||||||
format!("pull --ff --no-rebase --no-edit --commit {remote}"),
|
|
||||||
)?;
|
|
||||||
execute(repo_path, format!("push {remote}"))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@ -26,7 +26,7 @@ pub fn task_msg(msg: &str, task: &Task, id: usize) -> String {
|
|||||||
format!(
|
format!(
|
||||||
"{} task: {}({})",
|
"{} task: {}({})",
|
||||||
msg,
|
msg,
|
||||||
task.title.blue(),
|
task.title_string().blue(),
|
||||||
id.to_string().cyan()
|
id.to_string().cyan()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,26 @@
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use prettytable::{format, row, Row, Table};
|
use prettytable::{format, row, Row, Table};
|
||||||
|
|
||||||
use crate::cli::dates;
|
use crate::tasks::{Task, Tasks};
|
||||||
use crate::tasks::{Status, Task, Tasks};
|
|
||||||
|
|
||||||
pub fn calc_row(task: &Task, id: usize) -> Row {
|
pub fn calc_row(task: &Task, id: usize) -> Row {
|
||||||
if task.status == Status::Complete {
|
if task.is_complete() {
|
||||||
// Generate greyed out rows for complete tasks
|
// Generate greyed out rows for complete tasks
|
||||||
Row::from([
|
Row::from([
|
||||||
id.to_string().bright_black().italic(),
|
id.to_string().bright_black().italic(),
|
||||||
task.status.as_string().bright_black().italic(),
|
task.status_string().bright_black().italic(),
|
||||||
task.title.clone().bright_black().italic(),
|
task.title_string().bright_black().italic(),
|
||||||
dates::date_as_string(&task.when).bright_black().italic(),
|
task.when_string().bright_black().italic(),
|
||||||
dates::date_as_string(&task.deadline)
|
task.deadline_string().bright_black().italic(),
|
||||||
.bright_black()
|
|
||||||
.italic(),
|
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
// Generate normal colored rows for uncompleted tasks
|
// Generate normal colored rows for uncompleted tasks
|
||||||
Row::from([
|
Row::from([
|
||||||
id.to_string().cyan(),
|
id.to_string().cyan(),
|
||||||
task.status.as_string(),
|
task.status_string(),
|
||||||
task.title.clone().white(),
|
task.title_string(),
|
||||||
dates::date_as_string(&task.when),
|
task.when_string(),
|
||||||
dates::date_as_string(&task.deadline),
|
task.deadline_string(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +49,16 @@ pub fn task_table(task: &Task, id: usize) -> Table {
|
|||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.set_titles(row!["Item".magenta().bold(), "Value".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));
|
|
||||||
|
// Add rows
|
||||||
|
table.add_row(row!["ID".white().bold(), id.to_string().cyan()]);
|
||||||
|
table.add_row(row!["Status".white().bold(), task.status_string()]);
|
||||||
|
table.add_row(row!["Title".white().bold(), task.title_string()]);
|
||||||
|
table.add_row(row!["When".white().bold(), task.when_string(),]);
|
||||||
|
table.add_row(row!["Deadline".white().bold(), task.deadline_string(),]);
|
||||||
|
table.add_row(row!["Reminder".white().bold(), task.reminder_string(),]);
|
||||||
|
table.add_row(row!["Tags".white().bold(), &task.tags_string()]);
|
||||||
|
table.add_row(row!["Notes".white().bold(), &task.notes_string()]);
|
||||||
|
|
||||||
table
|
table
|
||||||
}
|
}
|
||||||
|
61
src/data.rs
61
src/data.rs
@ -1,61 +0,0 @@
|
|||||||
use dirs::home_dir;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::string::ToString;
|
|
||||||
|
|
||||||
use crate::cli::git;
|
|
||||||
use crate::cli::output;
|
|
||||||
use crate::tasks::Tasks;
|
|
||||||
|
|
||||||
pub fn save_tasks<P: AsRef<Path>>(path: P, tasks: &Tasks) -> Result<(), Box<dyn Error>> {
|
|
||||||
// Convert the tasks to TOML format
|
|
||||||
let data = toml::to_string_pretty(&tasks)?;
|
|
||||||
|
|
||||||
// Write the TOML to the file
|
|
||||||
fs::write(path, data)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_tasks<P: AsRef<Path> + ToString>(
|
|
||||||
path: P,
|
|
||||||
tasks_file: &str,
|
|
||||||
) -> Result<Tasks, Box<dyn Error>> {
|
|
||||||
let tasks_file_path = &format!("{}/{}", path.to_string(), tasks_file);
|
|
||||||
|
|
||||||
// Read TOML from the file
|
|
||||||
let data = fs::read_to_string(tasks_file_path)?;
|
|
||||||
|
|
||||||
// Load the tasks from TOML form
|
|
||||||
let tasks: Tasks = toml::from_str(&data)?;
|
|
||||||
|
|
||||||
Ok(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ensure_repo(path: &str, tasks_file: &str) -> Result<(), Box<dyn Error>> {
|
|
||||||
// Generate the path of the tasks file
|
|
||||||
let tasks_file_path = &format!("{}/{}", path, tasks_file);
|
|
||||||
|
|
||||||
// Check if the path exists
|
|
||||||
if !Path::new(path).exists() {
|
|
||||||
output::warning(format!(
|
|
||||||
"tasks repository {path} does not exist. creating..."
|
|
||||||
));
|
|
||||||
fs::create_dir_all(path).unwrap();
|
|
||||||
let tasks = Tasks::new(path, tasks_file);
|
|
||||||
save_tasks(tasks_file_path, &tasks).unwrap();
|
|
||||||
git::execute(path, String::from("init"))?;
|
|
||||||
git::execute(path, String::from("add ."))?;
|
|
||||||
output::success(format!("created tasks repo {path}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tasks_repo_string() -> String {
|
|
||||||
// Generate the path for the location of tasks
|
|
||||||
let home_dir = home_dir().unwrap();
|
|
||||||
let home_dir = home_dir.to_str().unwrap();
|
|
||||||
format!("{home_dir}/.local/share/inertia")
|
|
||||||
}
|
|
16
src/main.rs
16
src/main.rs
@ -1,6 +1,6 @@
|
|||||||
mod args;
|
mod args;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod data;
|
mod repo;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@ -10,29 +10,31 @@ use crate::args::TasksArgs;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Generate the file paths for tasks
|
// Generate the file paths for tasks
|
||||||
let repo_path = &data::tasks_repo_string();
|
let repo_path = repo::tasks_repo_string();
|
||||||
let tasks_file = "tasks";
|
let tasks_file_path = repo::tasks_file_path();
|
||||||
|
|
||||||
// If the tasks file doesn't exist, create it first
|
// If the tasks file doesn't exist, create it first
|
||||||
match data::ensure_repo(repo_path, tasks_file) {
|
match repo::ensure_repo(&repo_path) {
|
||||||
Ok(..) => (),
|
Ok(..) => (),
|
||||||
Err(error) => panic!("{} {:?}", "error:".red().bold(), error),
|
Err(error) => panic!("{} {:?}", "error:".red().bold(), error),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load tasks and check for any errors when loading the tasks
|
// Load tasks and check for any errors when loading the tasks
|
||||||
let mut tasks = match data::load_tasks(repo_path, tasks_file) {
|
let mut tasks = match repo::load_tasks(&tasks_file_path) {
|
||||||
Ok(tasks) => tasks,
|
Ok(tasks) => tasks,
|
||||||
Err(error) => panic!("{} {:?}", "error:".red().bold(), error),
|
Err(error) => panic!("{} {:?}", "error:".red().bold(), error),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
let arguments = TasksArgs::parse();
|
let arguments = TasksArgs::parse();
|
||||||
|
|
||||||
|
// Execute the inputted command line arguments
|
||||||
match cli::execute(&mut tasks, arguments) {
|
match cli::execute(&mut tasks, arguments) {
|
||||||
Ok(..) => (),
|
Ok(..) => (),
|
||||||
Err(error) => panic!("{} {:?}", "error:".red().bold(), error),
|
Err(error) => panic!("{} {:?}", "error:".red().bold(), error),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save any changes
|
// Save any changes
|
||||||
cli::git::execute(repo_path, String::from("add --all")).unwrap();
|
repo::save_tasks(&tasks_file_path, &tasks).unwrap();
|
||||||
data::save_tasks(&repo_path, &tasks).unwrap();
|
repo::execute(&repo_path, String::from("add --all")).unwrap();
|
||||||
}
|
}
|
||||||
|
97
src/repo.rs
Normal file
97
src/repo.rs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
use dirs::home_dir;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
|
use crate::cli::output;
|
||||||
|
use crate::tasks::Tasks;
|
||||||
|
|
||||||
|
const TASKS_FILE: &str = "tasks.toml";
|
||||||
|
|
||||||
|
pub fn execute(path: &str, command: String) -> Result<(), Box<dyn Error>> {
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args(["-C", path])
|
||||||
|
.args(command.split(' '))
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.stdout.is_empty() {
|
||||||
|
output::git(String::from_utf8(output.stdout).unwrap());
|
||||||
|
};
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
output::error(String::from_utf8(output.stderr).unwrap());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_tasks<P: AsRef<Path>>(path: P, tasks: &Tasks) -> Result<(), Box<dyn Error>> {
|
||||||
|
// Convert the tasks to TOML format
|
||||||
|
let data = toml::to_string_pretty(&tasks)?;
|
||||||
|
|
||||||
|
// Write the TOML to the file
|
||||||
|
fs::write(path, data)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_tasks<P: AsRef<Path> + ToString>(path: P) -> Result<Tasks, Box<dyn Error>> {
|
||||||
|
// Read TOML from the file
|
||||||
|
let data = fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
// Load the tasks from TOML form
|
||||||
|
let tasks: Tasks = toml::from_str(&data)?;
|
||||||
|
|
||||||
|
Ok(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_repo(path: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
// Generate the path of the tasks file
|
||||||
|
let tasks_file_path = tasks_file_path();
|
||||||
|
|
||||||
|
// Check if the path exists
|
||||||
|
if !Path::new(path).exists() {
|
||||||
|
output::warning(format!(
|
||||||
|
"tasks repository {path} does not exist. creating..."
|
||||||
|
));
|
||||||
|
|
||||||
|
// Create the directory
|
||||||
|
fs::create_dir_all(path).unwrap();
|
||||||
|
// Generate a new empty tasks structure
|
||||||
|
let tasks = Tasks::new(path, TASKS_FILE);
|
||||||
|
|
||||||
|
// Save the tasks
|
||||||
|
save_tasks(tasks_file_path, &tasks).unwrap();
|
||||||
|
|
||||||
|
// Create the git repository
|
||||||
|
execute(path, String::from("init"))?;
|
||||||
|
execute(path, String::from("add --all"))?;
|
||||||
|
|
||||||
|
// Success
|
||||||
|
output::success(format!("created tasks repo {path}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tasks_repo_string() -> String {
|
||||||
|
// Generate the path for the location of tasks
|
||||||
|
let home_dir = home_dir().unwrap();
|
||||||
|
let home_dir = home_dir.to_str().unwrap();
|
||||||
|
format!("{home_dir}/.local/share/inertia")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tasks_file_path() -> String {
|
||||||
|
format!("{}/{}", tasks_repo_string(), TASKS_FILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync(repo_path: &str, remote: String) -> Result<(), Box<dyn Error>> {
|
||||||
|
execute(
|
||||||
|
repo_path,
|
||||||
|
format!("pull --ff --no-rebase --no-edit --commit {remote}"),
|
||||||
|
)?;
|
||||||
|
execute(repo_path, format!("push {remote}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
213
src/tasks.rs
213
src/tasks.rs
@ -1,16 +1,37 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::{Local, NaiveDateTime};
|
||||||
use colored::*;
|
use colored::{ColoredString, Colorize};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TasksError(String);
|
pub struct TasksError(String);
|
||||||
|
|
||||||
|
impl TasksError {
|
||||||
|
pub fn no_task(id: usize) -> TasksError {
|
||||||
|
TasksError(format!("couldn't find task with id {}", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_tasks() -> TasksError {
|
||||||
|
TasksError(String::from("no tasks available"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Inbox,
|
Inbox, // When you create a new task without a when date
|
||||||
Pending,
|
Pending, // When you give a task a when date
|
||||||
Active,
|
Active, // When you have started a task
|
||||||
Complete,
|
Complete, // When a task is completed
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
pub fn as_string(&self) -> ColoredString {
|
||||||
|
match self {
|
||||||
|
Status::Inbox => "📮 Inbox".blue(),
|
||||||
|
Status::Pending => "📅 Pending".yellow(),
|
||||||
|
Status::Active => "🕑 Active".red(),
|
||||||
|
Status::Complete => "📗 Complete".green(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@ -24,21 +45,6 @@ pub struct Task {
|
|||||||
pub reminder: Option<NaiveDateTime>, // The datetime a reminder will alert you
|
pub reminder: Option<NaiveDateTime>, // The datetime a reminder will alert you
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct Tasks {
|
|
||||||
pub path: String, // Path to the tasks repository
|
|
||||||
pub file: String, // Path to the tasks file in the repository
|
|
||||||
pub tasks: Option<Vec<Task>>, // All the tasks in one vector
|
|
||||||
}
|
|
||||||
|
|
||||||
fn task_not_found(id: usize) -> TasksError {
|
|
||||||
TasksError(format!("couldn't find task with id {}", id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn no_tasks_available() -> TasksError {
|
|
||||||
TasksError(String::from("no tasks available"))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
title: String,
|
title: String,
|
||||||
@ -88,6 +94,9 @@ impl Task {
|
|||||||
|
|
||||||
if let Some(when) = when {
|
if let Some(when) = when {
|
||||||
self.when = Some(when);
|
self.when = Some(when);
|
||||||
|
if self.is_inbox() {
|
||||||
|
self.pend()
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(deadline) = deadline {
|
if let Some(deadline) = deadline {
|
||||||
@ -98,22 +107,118 @@ impl Task {
|
|||||||
self.reminder = Some(reminder);
|
self.reminder = Some(reminder);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start(&mut self) {
|
impl Task {
|
||||||
self.status = Status::Active;
|
pub fn inbox(&mut self) {
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&mut self) {
|
|
||||||
if self.when.is_some() {
|
|
||||||
self.status = Status::Inbox;
|
self.status = Status::Inbox;
|
||||||
} else {
|
self.when = None;
|
||||||
self.status = Status::Pending;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(&mut self) {
|
pub fn complete(&mut self) {
|
||||||
self.status = Status::Complete;
|
self.status = Status::Complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
self.status = Status::Active;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pend(&mut self) {
|
||||||
|
self.status = Status::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
if self.when.is_some() {
|
||||||
|
self.status = Status::Pending;
|
||||||
|
} else {
|
||||||
|
self.status = Status::Inbox;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
pub fn is_complete(&self) -> bool {
|
||||||
|
self.status == Status::Complete
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_active(&self) -> bool {
|
||||||
|
self.status == Status::Active
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_pending(&self) -> bool {
|
||||||
|
self.status == Status::Pending
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_inbox(&self) -> bool {
|
||||||
|
self.status == Status::Inbox
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
fn date_string(&self, date: &Option<NaiveDateTime>) -> ColoredString {
|
||||||
|
if let Some(date) = date {
|
||||||
|
let date = date.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 {
|
||||||
|
// No date available
|
||||||
|
"N/A".bright_black()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn when_string(&self) -> ColoredString {
|
||||||
|
self.date_string(&self.when)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deadline_string(&self) -> ColoredString {
|
||||||
|
self.date_string(&self.deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reminder_string(&self) -> ColoredString {
|
||||||
|
self.date_string(&self.reminder)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title_string(&self) -> ColoredString {
|
||||||
|
self.title.white()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_string(&self) -> ColoredString {
|
||||||
|
self.status.as_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tags_string(&self) -> ColoredString {
|
||||||
|
if let Some(tags) = &self.tags {
|
||||||
|
tags.join(", ").white()
|
||||||
|
} else {
|
||||||
|
"N/A".bright_black()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notes_string(&self) -> ColoredString {
|
||||||
|
if let Some(notes) = &self.notes {
|
||||||
|
notes.white()
|
||||||
|
} else {
|
||||||
|
"N/A".bright_black()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Tasks {
|
||||||
|
pub path: String, // Path to the tasks repository
|
||||||
|
pub file: String, // Path to the tasks file in the repository
|
||||||
|
pub tasks: Option<Vec<Task>>, // All the tasks in one vector
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tasks {
|
impl Tasks {
|
||||||
@ -124,25 +229,32 @@ impl Tasks {
|
|||||||
tasks: None,
|
tasks: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn task_exists(&self, id: usize) -> bool {
|
impl Tasks {
|
||||||
id < self.len()
|
/// Checks if tasks are empty
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_task(&mut self, id: usize) -> Result<&mut Task, TasksError> {
|
/// Checks if a task exists from an id
|
||||||
if self.is_empty() {
|
pub fn exists(&self, id: usize) -> bool {
|
||||||
Err(no_tasks_available())
|
id < self.len()
|
||||||
} else if self.task_exists(id) {
|
|
||||||
Ok(&mut self.tasks.as_mut().unwrap()[id])
|
|
||||||
} else {
|
|
||||||
Err(task_not_found(id))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a task from an id
|
||||||
|
pub fn task(&mut self, id: usize) -> Result<&mut Task, TasksError> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Err(TasksError::no_tasks())
|
||||||
|
} else if self.exists(id) {
|
||||||
|
Ok(&mut self.tasks.as_mut().unwrap()[id])
|
||||||
|
} else {
|
||||||
|
Err(TasksError::no_task(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tasks {
|
||||||
pub fn push(&mut self, task: Task) {
|
pub fn push(&mut self, task: Task) {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
self.tasks = Some(vec![task]);
|
self.tasks = Some(vec![task]);
|
||||||
@ -152,11 +264,11 @@ impl Tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, id: usize) -> Result<(), TasksError> {
|
pub fn remove(&mut self, id: usize) -> Result<(), TasksError> {
|
||||||
if self.task_exists(id) {
|
if self.exists(id) {
|
||||||
self.tasks.as_mut().unwrap().remove(id);
|
self.tasks.as_mut().unwrap().remove(id);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(task_not_found(id))
|
Err(TasksError::no_task(id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,21 +282,10 @@ 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(no_tasks_available())
|
Err(TasksError::no_tasks())
|
||||||
} else {
|
} else {
|
||||||
self.tasks = None;
|
self.tasks = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Status {
|
|
||||||
pub fn as_string(&self) -> ColoredString {
|
|
||||||
match self {
|
|
||||||
Status::Inbox => "📮 Inbox".blue(),
|
|
||||||
Status::Pending => "📅 Pending".yellow(),
|
|
||||||
Status::Active => "🕑 Active".red(),
|
|
||||||
Status::Complete => "📗 Complete".green(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user