tto 0.1.5

`to` is a small Rust CLI for project-scoped TODO lists. It walks up from your current working directory, finds the nearest `.todo` file, and operates on that task list automatically.
Documentation
use std::fmt::{self, Display, Formatter};
use std::io;
use std::path::PathBuf;

pub type Result<T> = std::result::Result<T, AppError>;

#[derive(Debug)]
pub enum AppError {
    Io(io::Error),
    InvalidArgs(String),
    TodoNotFound(PathBuf),
    AlreadyInitialized(PathBuf),
    InvalidTaskIndex { index: usize, len: usize },
    MalformedTodoLine { line: usize, content: String },
    NotGitRepository(PathBuf),
    GitCommandFailed(String),
    CommandFailed(String),
    EmptyTask,
}

impl Display for AppError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Io(err) => write!(f, "{err}"),
            Self::InvalidArgs(message) => write!(f, "{message}"),
            Self::TodoNotFound(path) => write!(
                f,
                "no `.todo` file found from {} upward; run `to init` in your project root",
                path.display()
            ),
            Self::AlreadyInitialized(path) => {
                write!(f, "a `.todo` file already exists at {}", path.display())
            }
            Self::InvalidTaskIndex { index, len } => {
                write!(f, "task {index} is out of range; expected a number between 1 and {len}")
            }
            Self::MalformedTodoLine { line, content } => write!(
                f,
                "invalid `.todo` format on line {line}: expected `[ ] task` or `[x] task`, got `{content}`"
            ),
            Self::NotGitRepository(path) => write!(
                f,
                "{} is not inside a git repository; `to scan` only works on git-tracked files",
                path.display()
            ),
            Self::GitCommandFailed(message) => write!(f, "{message}"),
            Self::CommandFailed(message) => write!(f, "{message}"),
            Self::EmptyTask => write!(f, "task text cannot be empty"),
        }
    }
}

impl std::error::Error for AppError {}

impl From<io::Error> for AppError {
    fn from(value: io::Error) -> Self {
        Self::Io(value)
    }
}