treeflow 0.2.1

CLI tool for simplified Git worktree management to speed up switching contexts when working collaboratively.
Documentation
use std::ffi::OsStr;
use clap::{arg, Command, ArgMatches, Id};
use std::path::PathBuf;
use clap::builder::{EnumValueParser, PathBufValueParser, ValueParser};
use clap_complete::{ArgValueCompleter, CompletionCandidate, ValueHint};
use crate::cli::completers;
use treeflow::core::config::Config;
use treeflow::utils::Shell;

pub fn command(config: &Config) -> Command {
    Command::new("treeflow")
        .version(env!("CARGO_PKG_VERSION"))
        .subcommands(subcommands(config))
        .subcommand_required(true)
}

pub fn subcommands(config: &Config) -> Vec<Command> {
    let mut commands = vec![
        Command::new("config")
            .about("Prints the current configuration to the screen."),
        Command::new("init")
            .about("Prints the shell function used to initialise treeflow.")
            .arg(arg!(<SHELL>).value_parser(EnumValueParser::<Shell>::new()).help("Shell to initialise for"))
            .arg(arg!(--"enable-shorthands").short('s').help("Enable shell aliases for treeflow commands")),
        Command::new("worktype")
            .about("Manage work types.")
            .subcommands(worktype_commands()),
        Command::new("project")
            .about("Manage a project")
            .subcommands(project_commands()),
        Command::new("primary")
            .about("Navigate to the primary repository directory.")
    ];

    commands.extend(start_on_work_type_commands(config));

    commands.extend(vec![
        Command::new("review")
            .about("Review an existing branch as a worktree.")
            .arg(arg!(<BRANCH>).add(ArgValueCompleter::new(completers::branches)).help("Branch to review")),
        Command::new("finish")
            .about("Finish a piece of work and remove the associated worktree and move back to the main repository.")
            .arg(arg!(--force).short('f').help("Force removal of worktree even if it contains untracked files")),
    ]);

    commands
}

fn worktype_commands() -> Vec<Command> {
    vec![
        Command::new("list")
            .about("List all configured work types."),
        Command::new("add")
            .about("Add a new type of work")
            .arg(arg!(<NAME>).add(ArgValueCompleter::new(completers::worktypes)).help("Name of the type of work"))
            .arg(arg!(--prefix <PREFIX>).short('p').required(true).help("Prefix to branches for some work")),
        Command::new("remove")
            .about("Remove a work type")
            .arg(arg!(<NAME>).add(ArgValueCompleter::new(completers::worktypes)).help("Name of the type of work to remove")),
    ]
}

fn project_commands() -> Vec<Command> {
    vec![
        Command::new("list")
            .about("List all configured projects"),
        Command::new("add")
            .about("Add a repository as a treeflow project")
            .arg(arg!(<REPOSITORY_DIR>).value_parser(PathBufValueParser::new()).value_hint(ValueHint::DirPath).help("Path to repository"))
            .arg(arg!(--"worktrees-dir" <PATH>).short('d').value_parser(PathBufValueParser::new()).value_hint(ValueHint::DirPath).help("Path to directory where worktrees are stored for a project")),
        Command::new("remove")
            .about("Remove a project from the configuration")
            .arg(arg!(<REPOSITORY_DIR>).value_parser(PathBufValueParser::new()).value_hint(ValueHint::DirPath).help("Path to repository")),
    ]
}

fn start_on_work_type_commands(config: &Config) -> Vec<Command> {
    config.work_types.iter()
        .map(|work_type| {
            // Use Box::leak to create a static strings
            let static_name: &'static str = Box::leak(work_type.type_name.clone().into_boxed_str());
            let static_prefix: &'static str = Box::leak(work_type.prefix.clone().into_boxed_str());
            let work_names = |current: &OsStr| -> Vec<CompletionCandidate> { completers::work_names(current, static_prefix) };
            Command::new(static_name)
                .about(format!("Start a {} work item.", static_name))
                .arg(arg!(<NAME>).help("Name of the work to begin").add(ArgValueCompleter::new(work_names)))
        })
        .collect()
}