use std::{fs, path::PathBuf};
use anyhow::{anyhow, Ok, Result};
use clap::{Parser, Subcommand};
use toddi::{
ingress::TaskSrc,
model::{CompletedTaskList, Project, TaskList},
};
mod config;
mod tui;
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[arg(short, long)]
todo_txts: Option<Vec<PathBuf>>,
#[arg(short, long)]
config_file: Option<PathBuf>,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Focus { project: Option<String> },
Projects { projects: Vec<String> },
}
fn main() -> Result<()> {
let args = Cli::parse();
let config = load_and_set_configuration(&args)?;
let (mut todos, mut dones) = generate_data_structure(&config)?;
match &args.command {
Some(Commands::Projects { projects }) => {
if projects.is_empty() {
tui::display_no_projects();
}
for project in projects {
if let Some(project) =
Project::search_project(project.to_string(), todos.clone(), dones.clone())
{
tui::display_project_status(&project);
} else {
tui::display_project_not_found(project);
}
}
}
Some(Commands::Focus { project }) => loop {
match project {
Some(project) => {
if let Some(project) =
Project::search_project(project.to_string(), todos.clone(), dones.clone())
{
tui::display_project_status(&project);
let buffer = tui::query_user()?;
match buffer.trim() {
"y" => {
println!("Well done! Next task...");
project.complete_current_task()?;
(todos, dones) = generate_data_structure(&config)?;
}
"n" => println!("Then why did you bother answering?"),
"q" => {
println!("Alright, we are done for now.");
break;
}
_ => println!("Wrong input, try again."),
}
} else {
tui::display_project_not_found(project);
}
}
None => {
show_task(&todos, &dones)?;
let buffer = tui::query_user()?;
match buffer.trim() {
"y" => {
println!("Well done! Next task...");
todos.complete_current_task()?;
(todos, dones) = generate_data_structure(&config)?;
}
"n" => println!("Then why did you bother answering?"),
"q" => {
println!("Alright, we are done for now.");
break;
}
_ => println!("Wrong input, try again."),
}
}
}
},
None => show_task(&todos, &dones)?,
}
Ok(())
}
fn show_task(
todos: &TaskList,
dones: &CompletedTaskList,
) -> std::result::Result<(), anyhow::Error> {
if let Some(task) = todos.clone().get_current_task() {
if task.project.is_empty() {
tui::display_task(task);
} else if let Some(project) =
Project::search_project(task.project.clone(), todos.clone(), dones.clone())
{
tui::display_project_status(&project);
} else {
return Err(anyhow!("It should not be possible to match a project in a task and then not be able to retrieve it!\\ntask: {:?}", task));
}
} else {
tui::display_empty_todo_list();
}
Ok(())
}
fn generate_data_structure(config: &config::Config) -> Result<(TaskList, CompletedTaskList)> {
let task_list = TaskList::new(TaskSrc::from_file(config.todo_files.clone())?)?;
let done_files = extrapolate_done_files_path(config.todo_files.clone());
for file in done_files.clone() {
if !fs::exists(&file)? {
if let Some(file_name) = file.to_str() {
eprintln!("could not find `{}`, creating it.", file_name);
fs::write(&file, "")?;
} else {
return Err(anyhow!(
"Failed to access string representation of done.txt path."
));
}
}
}
Ok((
task_list,
CompletedTaskList::new(TaskSrc::from_file(done_files.clone())?)?,
))
}
fn extrapolate_done_files_path(todo_files: Vec<PathBuf>) -> Vec<std::path::PathBuf> {
let mut done_files: Vec<PathBuf> = vec![];
for file in todo_files {
if let Some(parent) = file.parent() {
let mut path_buf = parent.to_path_buf();
path_buf.push(toddi::DONE_FILE);
done_files.push(path_buf);
}
}
done_files
}
fn load_and_set_configuration(args: &Cli) -> Result<config::Config> {
let mut config_file_path: PathBuf;
if let Some(file) = args.config_file.as_deref() {
config_file_path = file.to_path_buf();
} else {
config_file_path = get_configdir()?;
config_file_path.push("toddi");
config_file_path.push("config.toml");
}
let mut config = config::Config::load(config_file_path)?;
apply_cli_overrides(args, &mut config);
Ok(config)
}
fn apply_cli_overrides(args: &Cli, config: &mut config::Config) {
if let Some(input) = args.todo_txts.as_deref() {
config.todo_files = input.to_vec();
}
}
fn get_configdir() -> Result<PathBuf> {
if let Some(dir) = dirs::config_dir() {
Ok(dir)
} else {
Err(anyhow!(
"Issue with `dirs` crate: could not fetch configuration directory."
))
}
}