use clap::Parser;
use std::{env, fs, path::PathBuf, process};
mod commands;
mod config;
mod flags;
mod formatting;
fn main() {
let run = || -> Result<(), toado::Error> {
let args = flags::Cli::parse();
let config_path = args.config.map(PathBuf::from);
let app_config = match config::get_config(config_path) {
Ok(c) => c,
Err(e) => {
eprintln!("Failed to load config: {e}");
return Err(e);
}
};
let database_path = match init_database_path(args.file) {
Ok(d) => d,
Err(e) => {
eprintln!("Failed to initialize application directory: {e}");
return Err(e);
}
};
let app = match toado::Server::open(database_path) {
Ok(app) => app,
Err(e) => {
eprintln!("Failed to create application server: {e}");
return Err(e);
}
};
if let Err(e) = app.init() {
eprintln!("Failed to initialize application server: {e}");
return Err(e);
};
if args.search.is_some() || args.command.is_some() {
let res = {
if let Some(search) = args.search {
handle_search(
flags::SearchArgs {
term: search,
task: args.task,
project: args.project,
verbose: args.verbose,
},
app,
&app_config,
)
} else if let Some(command) = args.command {
handle_command(command, app, &app_config)
} else {
Ok(None)
}
};
match res {
Ok(Some(message)) => println!("{message}"),
Err(e) => {
eprintln!("Failed to execute command: {e}");
return Err(e);
}
_ => {}
};
return Ok(());
}
println!("toado");
Ok(())
};
if let Err(e) = run() {
if let Some(e) = e.source() {
eprintln!("Caused by: {e}")
}
process::exit(1)
}
}
fn init_database_path(path_string: Option<String>) -> Result<PathBuf, toado::Error> {
if let Some(path_string) = path_string {
let path = PathBuf::from(path_string);
Ok(path)
} else {
let home_dir = env::var("HOME")?;
let mut path = PathBuf::from(format!("{home_dir}/.local/share/toado/"));
fs::create_dir_all(path.clone())?;
path.push("database");
Ok(path)
}
}
fn handle_command(
command: flags::Commands,
app: toado::Server,
config: &config::Config,
) -> Result<Option<String>, toado::Error> {
let message = match command {
flags::Commands::Search(args) => handle_search(args, app, config)?,
flags::Commands::Add(args) => handle_add(args, app, config)?,
flags::Commands::Delete(args) => handle_delete(args, app, config)?,
flags::Commands::Update(args) => handle_update(args, app, config)?,
flags::Commands::Ls(args) => handle_ls(args, app, config)?,
flags::Commands::Check(args) => handle_check(args, app, config)?,
flags::Commands::Assign(args) => handle_assign(args, app, config)?,
};
Ok(message)
}
fn handle_search(
args: flags::SearchArgs,
app: toado::Server,
config: &config::Config,
) -> Result<Option<String>, toado::Error> {
if args.task || !args.project {
commands::search_tasks(args, app, config)
} else {
Err(Into::into("search is not implemented for projects"))
}
}
fn handle_add(
args: flags::AddArgs,
app: toado::Server,
_config: &config::Config,
) -> Result<Option<String>, toado::Error> {
if args.task || !args.project {
let (task_id, task_name) = commands::create_task(args, app)?;
Ok(Some(format!(
"Created task '{task_name}' with id '{task_id}'"
)))
} else {
let (project_id, project_name) = commands::create_project(args, app)?;
Ok(Some(format!(
"Created project '{project_name}' with id '{project_id}'"
)))
}
}
fn handle_delete(
args: flags::DeleteArgs,
app: toado::Server,
config: &config::Config,
) -> Result<Option<String>, toado::Error> {
if args.task || !args.project {
match commands::delete_task(args, app, config)? {
Some(id) => Ok(Some(format!("Deleted task with id {id}"))),
None => Ok(None),
}
} else {
match commands::delete_project(args, app, config)? {
Some(id) => Ok(Some(format!("Deleted project with id {id}"))),
None => Ok(None),
}
}
}
fn handle_update(
args: flags::UpdateArgs,
app: toado::Server,
config: &config::Config,
) -> Result<Option<String>, toado::Error> {
Ok(Some(format!(
"{} row(s) updated",
if args.task || !args.project {
commands::update_task(args, app, config)?
} else {
commands::update_project(args, app, config)?
}
)))
}
fn handle_ls(
mut args: flags::ListArgs,
app: toado::Server,
config: &config::Config,
) -> Result<Option<String>, toado::Error> {
let mut verbose = config.list.default_verbose;
if args.verbose {
verbose = !verbose;
}
args.verbose = verbose;
if args.task || !args.project {
commands::list_tasks(args, app, config)
} else {
commands::list_projects(args, app, config)
}
}
fn handle_check(
args: flags::CheckArgs,
app: toado::Server,
config: &config::Config,
) -> Result<Option<String>, toado::Error> {
let (task_name, task_status) = commands::check_task(args, app, config)?;
Ok(Some(format!(
"Set '{task_name}' to {}",
task_status.to_string().to_uppercase()
)))
}
fn handle_assign(
args: flags::AssignArgs,
app: toado::Server,
config: &config::Config,
) -> Result<Option<String>, toado::Error> {
let (pairs, action) = if !args.unassign {
(
if !args.no_select {
commands::assign_multiple_tasks(args, app, config)?
} else {
vec![commands::assign_task(args, app)?]
},
"assigned to",
)
} else {
(
if !args.no_select {
commands::unassign_multiple_tasks(args, app, config)?
} else {
vec![commands::unassign_task(args, app)?]
},
"unassigned from",
)
};
let message = pairs
.into_iter()
.map(|(task_name, project_name)| format!("'{task_name}' {action} '{project_name}'"))
.collect::<Vec<String>>()
.join("\n");
Ok(Some(message))
}