oh-my-todo 0.2.0

Local-first terminal task manager with a mouse-first TUI and CLI.
Documentation
use crate::application::bootstrap::AppContext;
use crate::application::commands::{
    AddTaskLogCommand, ArchiveSpaceCommand, ArchiveTaskCommand, CreateSpaceCommand,
    CreateTaskCommand, EditTaskCommand, PurgeSpaceCommand, PurgeTaskCommand, RenameSpaceCommand,
    RestoreSpaceCommand, RestoreTaskCommand, SetCurrentSpaceCommand, UpdateTaskStatusCommand,
};
use crate::application::error::AppError;
use crate::application::queries::{ListSpacesQuery, ListTasksQuery, ShowSpaceQuery, ShowTaskQuery};
use crate::cli::output;
use crate::cli::parser::{
    RootCommand, SpaceCommand, TaskCommand, TaskLogCommand, TaskStatusCommand, TodoCli,
};
use crate::tui::{self, LaunchOptions};

pub fn dispatch(context: &AppContext, cli: TodoCli) -> Result<(), AppError> {
    match cli.command {
        RootCommand::Doctor => {
            let report = context.maintenance_service.doctor()?;
            println!("{}", output::render_doctor_report(&report));
            Ok(())
        }
        RootCommand::Tui(args) => {
            let space_id = match args.space.as_deref() {
                Some(reference) => Some(context.space_service.resolve_space(reference, false)?.id),
                None => None,
            };

            tui::run_with_options(
                context,
                LaunchOptions {
                    space_id,
                    view: args.view.map(Into::into),
                    sort: args.sort.map(Into::into),
                },
            )
        }
        RootCommand::Space(space_args) => match space_args.command {
            SpaceCommand::Add(args) => {
                let space = context
                    .space_service
                    .create_space(CreateSpaceCommand { name: args.name })?;
                println!("{}", output::format_created_space(&space));
                launch_tui_if_needed(context, args.tui, Some(space.id), None, None)
            }
            SpaceCommand::List(args) => {
                let spaces = context.space_service.list_spaces(ListSpacesQuery {
                    include_archived: args.include_archived,
                })?;
                println!("{}", output::render_spaces(&spaces));
                Ok(())
            }
            SpaceCommand::Show(args) => {
                let details = context.space_service.show_space(ShowSpaceQuery {
                    space_ref: args.space_ref,
                })?;
                println!("{}", output::render_space(&details));
                Ok(())
            }
            SpaceCommand::Use(args) => {
                let space = context.space_service.use_space(SetCurrentSpaceCommand {
                    space_ref: args.space_ref,
                })?;
                println!("{}", output::format_used_space(&space));
                launch_tui_if_needed(context, args.tui, Some(space.id), None, None)
            }
            SpaceCommand::Rename(args) => {
                let space = context.space_service.rename_space(RenameSpaceCommand {
                    space_ref: args.space_ref,
                    new_name: args.new_name,
                })?;
                println!("{}", output::format_renamed_space(&space));
                launch_tui_if_needed(context, args.tui, Some(space.id), None, None)
            }
            SpaceCommand::Archive(args) => {
                let outcome = context.space_service.archive_space(ArchiveSpaceCommand {
                    space_ref: args.space_ref,
                })?;
                let space = outcome.root_space.expect("space archive returns space");
                println!("{}", output::format_archived_space(&space));
                launch_tui_if_needed(context, args.tui, Some(space.id), None, None)
            }
            SpaceCommand::Restore(args) => {
                let outcome = context.space_service.restore_space(RestoreSpaceCommand {
                    space_ref: args.space_ref,
                })?;
                let space = outcome.root_space.expect("space restore returns space");
                println!("{}", output::format_restored_space(&space));
                launch_tui_if_needed(context, args.tui, Some(space.id), None, None)
            }
            SpaceCommand::Purge(args) => {
                let _ = args.force;
                let outcome = context.space_service.purge_space(PurgeSpaceCommand {
                    space_ref: args.space_ref,
                })?;
                let space = outcome.root_space.expect("space purge returns prior space");
                println!("{}", output::format_purged_space(&space));
                Ok(())
            }
        },
        RootCommand::Task(task_args) => match task_args.command {
            TaskCommand::Add(args) => {
                let task = context.task_service.create_task(CreateTaskCommand {
                    title: args.title,
                    space_ref: args.space,
                    description: args.description,
                    parent_ref: args.parent,
                    status: args
                        .status
                        .map(Into::into)
                        .unwrap_or(crate::domain::TaskStatus::Todo),
                })?;
                let space = context.space_service.load_space(&task.space_id)?;
                println!("{}", output::format_created_task(&task, &space));
                launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
            }
            TaskCommand::List(args) => {
                let result = context.task_service.list_tasks(ListTasksQuery {
                    space_ref: args.space,
                    view: args.view.map(Into::into),
                    sort: args.sort.map(Into::into),
                    allow_archived_space: false,
                })?;
                println!("{}", output::render_task_list(&result));
                Ok(())
            }
            TaskCommand::Show(args) => {
                let details = context.task_service.show_task(ShowTaskQuery {
                    task_ref: args.task_ref,
                })?;
                println!("{}", output::render_task(&details));
                Ok(())
            }
            TaskCommand::Edit(args) => {
                let task = context.task_service.edit_task(EditTaskCommand {
                    task_ref: args.task_ref,
                    title: args.title,
                    description: args.description,
                    clear_description: args.clear_desc,
                    status: args.status.map(Into::into),
                    parent_ref: args.parent,
                    clear_parent: args.clear_parent,
                    space_ref: args.space,
                })?;
                println!("{}", output::format_updated_task(&task));
                launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
            }
            TaskCommand::Status(status_args) => match status_args.command {
                TaskStatusCommand::Set(args) => {
                    let task = context
                        .task_service
                        .set_task_status(UpdateTaskStatusCommand {
                            task_ref: args.task_ref,
                            status: args.status.into(),
                        })?;
                    println!("{}", output::format_task_status(&task));
                    launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
                }
            },
            TaskCommand::Done(args) => {
                let task = context
                    .task_service
                    .set_task_status(UpdateTaskStatusCommand {
                        task_ref: args.task_ref,
                        status: crate::domain::TaskStatus::Done,
                    })?;
                println!("{}", output::format_task_status(&task));
                launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
            }
            TaskCommand::Close(args) => {
                let task = context
                    .task_service
                    .set_task_status(UpdateTaskStatusCommand {
                        task_ref: args.task_ref,
                        status: crate::domain::TaskStatus::Close,
                    })?;
                println!("{}", output::format_task_status(&task));
                launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
            }
            TaskCommand::Archive(args) => {
                let outcome = context.task_service.archive_task(ArchiveTaskCommand {
                    task_ref: args.task_ref,
                })?;
                let task = outcome.root_task.expect("task archive returns task");
                println!(
                    "{}",
                    output::format_archived_task(&task, outcome.affected_count)
                );
                launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
            }
            TaskCommand::Restore(args) => {
                let outcome = context.task_service.restore_task(RestoreTaskCommand {
                    task_ref: args.task_ref,
                })?;
                let task = outcome.root_task.expect("task restore returns task");
                println!(
                    "{}",
                    output::format_restored_task(&task, outcome.affected_count)
                );
                launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
            }
            TaskCommand::Log(log_args) => match log_args.command {
                TaskLogCommand::Add(args) => {
                    let task = context.task_service.add_task_log(AddTaskLogCommand {
                        task_ref: args.task_ref,
                        message: args.text,
                    })?;
                    println!("{}", output::format_logged_task(&task));
                    launch_tui_if_needed(context, args.tui, Some(task.space_id), None, None)
                }
            },
            TaskCommand::Purge(args) => {
                let _ = args.force;
                let outcome = context.task_service.purge_task(PurgeTaskCommand {
                    task_ref: args.task_ref,
                    recursive: args.recursive,
                })?;
                let task = outcome.root_task.expect("task purge returns prior task");
                println!(
                    "{}",
                    output::format_purged_task(&task, outcome.affected_count)
                );
                Ok(())
            }
        },
    }
}

fn launch_tui_if_needed(
    context: &AppContext,
    enabled: bool,
    space_id: Option<crate::domain::SpaceId>,
    view: Option<crate::domain::ViewMode>,
    sort: Option<crate::domain::SortMode>,
) -> Result<(), AppError> {
    if enabled {
        tui::run_with_options(
            context,
            LaunchOptions {
                space_id,
                view,
                sort,
            },
        )
    } else {
        Ok(())
    }
}