tip-files 0.1.0

Tickets in project
Documentation
use std::{fmt::Display, io::stdout, path::PathBuf};

use clap::{Parser, Subcommand, builder::PossibleValue};
use tip_files::Tips;

/// Tickets in project T.I.P.
///
/// Manage tickets as plain text files within the project.
#[derive(Parser, Clone, Debug)]
struct Args {
    /// Specific tip reference
    tip: Option<String>,
    /// What to do with the referenced tip(s)
    #[command(subcommand)]
    cmd: Option<Command>,
}

#[derive(Subcommand, Clone, Debug)]
enum Command {
    /// List all tips, default only open ones
    #[clap(visible_alias("l"))]
    List {
        #[clap(short, default_value_t = FilterState::Open)]
        filter_state: FilterState,
    },
    /// View details of a tip
    #[clap(visible_alias("v"))]
    View { tip: String },
    /// New tip, providing title
    #[clap(visible_alias("n"))]
    New { title: String },
    /// Deletion of a tip
    #[clap(visible_alias("r"))]
    Remove { tip: String },
    /// Change the state of a tip to open
    #[clap(visible_alias("o"))]
    Open { tip: String },
    /// Change state of a tip to closed
    #[clap(visible_alias("c"))]
    Close { tip: String },
}

#[derive(Clone, Debug, Default)]
enum FilterState {
    #[default]
    Open,
    Closed,
    All,
}

impl FilterState {
    fn str(&self) -> &'static str {
        match self {
            FilterState::Open => "open",
            FilterState::Closed => "closed",
            FilterState::All => "all",
        }
    }

    fn filter(&self) -> &'static [&'static str] {
        match self {
            FilterState::Open => &["open"],
            FilterState::Closed => &["closed"],
            FilterState::All => &["open", "closed"],
        }
    }
}

impl Display for FilterState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.str())
    }
}

impl clap::ValueEnum for FilterState {
    fn value_variants<'a>() -> &'a [Self] {
        &[FilterState::Open, FilterState::Closed, FilterState::All]
    }

    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
        Some(PossibleValue::new(self.str()))
    }
}

fn main() {
    let mut args = Args::parse();
    let root = PathBuf::from("tip");
    if args.cmd.is_none() {
        if let Some(tip) = args.tip {
            args.cmd = Some(Command::View { tip })
        };
        args.cmd = Some(Command::List {
            filter_state: Default::default(),
        });
    }
    let mut out = stdout();
    let mut tips = Tips::new(root, &mut out);
    match args.cmd.unwrap_or(Command::List {
        filter_state: Default::default(),
    }) {
        Command::List { filter_state } => tips.list(filter_state.filter()),
        Command::View { tip } => tips.details(tip.as_str()),
        Command::New { title } => tips.create(title.as_str()),
        Command::Remove { tip } => tips.delete(tip.as_str()),
        Command::Open { tip } => tips.open(tip.as_str()),
        Command::Close { tip } => tips.close(tip.as_str()),
    }
}