use std::path::PathBuf;
use clap::builder::styling::{AnsiColor, Effects, Styles};
use clap::{ArgAction, Args, Parser, Subcommand};
use clap_complete::Shell;
const STYLES: Styles = Styles::styled()
.header(AnsiColor::Yellow.on_default().effects(Effects::BOLD))
.usage(AnsiColor::Yellow.on_default().effects(Effects::BOLD))
.literal(AnsiColor::Green.on_default().effects(Effects::BOLD))
.placeholder(AnsiColor::Cyan.on_default());
#[derive(Parser)]
#[command(name = "gemote", version, about = "Declarative git remote management.", styles = STYLES)]
pub struct Cli {
#[arg(long, global = true)]
pub config: Option<PathBuf>,
#[arg(long, global = true)]
pub repo: Option<PathBuf>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
Sync {
#[arg(long)]
dry_run: bool,
#[command(flatten)]
recursive: RecursiveFlag,
},
Save {
#[arg(long, short = 'f')]
force: bool,
#[command(flatten)]
recursive: RecursiveFlag,
},
Completions {
shell: Shell,
},
}
#[derive(Args, Debug, Clone, Default)]
pub struct RecursiveFlag {
#[arg(
long,
short = 'r',
action = ArgAction::SetTrue,
overrides_with = "no_recursive",
)]
recursive: bool,
#[arg(
long = "no-recursive",
action = ArgAction::SetTrue,
overrides_with = "recursive",
)]
no_recursive: bool,
}
impl RecursiveFlag {
pub fn resolve(&self) -> Option<bool> {
if self.recursive {
Some(true)
} else if self.no_recursive {
Some(false)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
fn sync_dry_run(cli: &Cli) -> bool {
match &cli.command {
Commands::Sync { dry_run, .. } => *dry_run,
_ => panic!("expected sync"),
}
}
fn save_force(cli: &Cli) -> bool {
match &cli.command {
Commands::Save { force, .. } => *force,
_ => panic!("expected save"),
}
}
fn sync_recursive(cli: &Cli) -> Option<bool> {
match &cli.command {
Commands::Sync { recursive, .. } => recursive.resolve(),
_ => panic!("expected sync"),
}
}
fn save_recursive(cli: &Cli) -> Option<bool> {
match &cli.command {
Commands::Save { recursive, .. } => recursive.resolve(),
_ => panic!("expected save"),
}
}
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
#[test]
fn parse_sync() {
let cli = Cli::try_parse_from(["gemote", "sync"]).unwrap();
assert!(!sync_dry_run(&cli));
assert_eq!(sync_recursive(&cli), None);
}
#[test]
fn parse_sync_dry_run() {
let cli = Cli::try_parse_from(["gemote", "sync", "--dry-run"]).unwrap();
assert!(sync_dry_run(&cli));
assert_eq!(sync_recursive(&cli), None);
}
#[test]
fn parse_sync_recursive() {
let cli = Cli::try_parse_from(["gemote", "sync", "--recursive"]).unwrap();
assert_eq!(sync_recursive(&cli), Some(true));
}
#[test]
fn parse_sync_recursive_short() {
let cli = Cli::try_parse_from(["gemote", "sync", "-r"]).unwrap();
assert_eq!(sync_recursive(&cli), Some(true));
}
#[test]
fn parse_sync_no_recursive() {
let cli = Cli::try_parse_from(["gemote", "sync", "--no-recursive"]).unwrap();
assert_eq!(sync_recursive(&cli), Some(false));
}
#[test]
fn parse_sync_last_flag_wins() {
let cli = Cli::try_parse_from(["gemote", "sync", "--recursive", "--no-recursive"]).unwrap();
assert_eq!(sync_recursive(&cli), Some(false));
}
#[test]
fn parse_save() {
let cli = Cli::try_parse_from(["gemote", "save"]).unwrap();
assert!(!save_force(&cli));
assert_eq!(save_recursive(&cli), None);
}
#[test]
fn parse_save_force() {
let cli = Cli::try_parse_from(["gemote", "save", "--force"]).unwrap();
assert!(save_force(&cli));
assert_eq!(save_recursive(&cli), None);
}
#[test]
fn parse_save_force_short() {
let cli = Cli::try_parse_from(["gemote", "save", "-f"]).unwrap();
assert!(save_force(&cli));
assert_eq!(save_recursive(&cli), None);
}
#[test]
fn parse_save_recursive() {
let cli = Cli::try_parse_from(["gemote", "save", "--recursive"]).unwrap();
assert_eq!(save_recursive(&cli), Some(true));
}
#[test]
fn parse_save_recursive_short() {
let cli = Cli::try_parse_from(["gemote", "save", "-r"]).unwrap();
assert_eq!(save_recursive(&cli), Some(true));
}
#[test]
fn parse_save_no_recursive() {
let cli = Cli::try_parse_from(["gemote", "save", "--no-recursive"]).unwrap();
assert_eq!(save_recursive(&cli), Some(false));
}
#[test]
fn parse_global_flags() {
let cli = Cli::try_parse_from([
"gemote",
"--config",
"/tmp/cfg",
"--repo",
"/tmp/repo",
"sync",
])
.unwrap();
assert_eq!(cli.config.unwrap(), PathBuf::from("/tmp/cfg"));
assert_eq!(cli.repo.unwrap(), PathBuf::from("/tmp/repo"));
}
#[test]
fn parse_completions_bash() {
let cli = Cli::try_parse_from(["gemote", "completions", "bash"]).unwrap();
assert!(matches!(
cli.command,
Commands::Completions { shell: Shell::Bash }
));
}
#[test]
fn parse_completions_zsh() {
let cli = Cli::try_parse_from(["gemote", "completions", "zsh"]).unwrap();
assert!(matches!(
cli.command,
Commands::Completions { shell: Shell::Zsh }
));
}
#[test]
fn parse_completions_fish() {
let cli = Cli::try_parse_from(["gemote", "completions", "fish"]).unwrap();
assert!(matches!(
cli.command,
Commands::Completions { shell: Shell::Fish }
));
}
#[test]
fn parse_completions_invalid_shell() {
assert!(Cli::try_parse_from(["gemote", "completions", "nushell"]).is_err());
}
}