tasks-cli-rs 0.6.1

Markdown-based TODO task management CLI: each task is a Markdown file with YAML front matter
use clap::Parser;

mod checkbox;
mod cli;
mod commands;
mod config;
mod editor;
mod error;
mod filter;
mod library;
mod model;
mod storage;

fn main() {
    let cli = cli::Cli::parse();
    if let Err(err) = dispatch(cli) {
        eprintln!("error: {err}");
        std::process::exit(1);
    }
}

pub(crate) fn dispatch(cli: cli::Cli) -> error::Result<()> {
    match cli.command {
        cli::Command::Repl => commands::repl::run()?,
        cli::Command::Info => {
            let config = config::Config::load()?;
            println!("base dir: {}", config::base_dir()?.display());
            println!("editor:   {}", config.editor.as_deref().unwrap_or("(auto)"));
        }
        cli::Command::Lib { command } => commands::lib::run(command)?,
        cli::Command::New { title, description, priority, tags, due, dir, template } => {
            commands::task::new(title, description, priority, tags, due, dir, template)?
        }
        cli::Command::Template { command } => match command {
            cli::TemplateCommand::List => commands::template::list()?,
            cli::TemplateCommand::Show { name } => commands::template::show(&name)?,
            cli::TemplateCommand::New { name, lib, editor } => {
                commands::template::new(&name, lib, editor)?
            }
            cli::TemplateCommand::Edit { name, editor } => {
                commands::template::edit(&name, editor)?
            }
            cli::TemplateCommand::Remove { name } => commands::template::remove(&name)?,
        },
        cli::Command::List { filter, quiet } => commands::task::list(filter, quiet)?,
        cli::Command::Batch { new_status, filter, dry_run, force } => {
            commands::task::batch(&new_status, filter, dry_run, force)?
        }
        cli::Command::Archive { filter, dry_run, force } => {
            commands::task::archive(filter, dry_run, force)?
        }
        cli::Command::Adopt { paths, all, dry_run, rename } => {
            commands::adopt::run(commands::adopt::AdoptArgs { paths, all, dry_run, rename })?
        }
        cli::Command::Search { query, status, tag } => {
            commands::task::search(query, status, tag)?
        }
        cli::Command::Tag { command } => match command {
            cli::TagCommand::Add { id, tags } => commands::task::tag_add(&id, tags)?,
            cli::TagCommand::Remove { id, tags } => commands::task::tag_remove(&id, tags)?,
            cli::TagCommand::List => commands::task::tag_list()?,
        },
        cli::Command::Show { id } => commands::task::show(&id)?,
        cli::Command::Delete { id, force } => commands::task::delete(&id, force)?,
        cli::Command::Start { id } => commands::task::set_status(&id, model::Status::InProgress)?,
        cli::Command::Done { id } => commands::task::set_status(&id, model::Status::Done)?,
        cli::Command::Cancel { id } => commands::task::set_status(&id, model::Status::Cancelled)?,
        cli::Command::Status { id, status } => {
            let status = status
                .parse::<model::Status>()
                .map_err(error::Error::InvalidTaskFile)?;
            commands::task::set_status(&id, status)?
        }
        cli::Command::Set { id, title, description, priority, due, tag_add, tag_remove } => {
            commands::task::set_fields(&id, title, description, priority, due, tag_add, tag_remove)?
        }
        cli::Command::Step { command } => commands::step::run(command)?,
        cli::Command::Body { command } => commands::body::run(command)?,
        cli::Command::Board { dir, tag, columns } => {
            let root = commands::task::active_library_root()?;
            let scope = match &dir {
                Some(d) => root.join(d),
                None => root,
            };
            let outcome = storage::load_library(&scope)?;
            let tasks: Vec<_> = outcome
                .tasks
                .into_iter()
                .filter(|t| tag.as_ref().is_none_or(|tg| t.task.meta.tags.contains(tg)))
                .collect();
            print!("{}", commands::board::render(&tasks, columns)?);
        }
        cli::Command::Remind { within, overdue, summary } => {
            commands::report::remind(within, overdue, summary)?
        }
        cli::Command::Overdue => commands::report::overdue()?,
        cli::Command::Stats => commands::report::stats()?,
        cli::Command::Completions { shell } => {
            use clap::CommandFactory;
            let shell: clap_complete::Shell = shell
                .parse()
                .map_err(|_| error::Error::InvalidTaskFile(format!("unknown shell '{shell}'")))?;
            clap_complete::generate(shell, &mut cli::Cli::command(), "tasks", &mut std::io::stdout());
        }
        cli::Command::FixNames { dry_run } => commands::task::fix_names(dry_run)?,
        cli::Command::Edit { id, editor, lib } => commands::task::edit(id, editor, lib)?,
    }
    Ok(())
}