use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
use ctl_core::{ColorLong, DryRunArgs, FormatArgs};
#[derive(Parser)]
#[command(
version,
about = "Stack-agnostic version PRs from Changesets-format fragments",
arg_required_else_help = true
)]
pub struct Cli {
#[command(flatten)]
pub format: FormatArgs,
#[command(flatten)]
pub color: ColorLong,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand)]
pub enum Command {
Instructions,
Status(StatusArgs),
Check(CheckArgs),
Prepare(PrepareArgs),
Publish(PublishArgs),
Pin(PublishArgs),
Assets(AssetsArgs),
Ci(CiArgs),
}
#[derive(Args)]
pub struct CiArgs {
#[arg(short = 'c', long, default_value = crate::config::FILE, value_hint = clap::ValueHint::FilePath)]
pub config: PathBuf,
#[arg(long, value_hint = clap::ValueHint::FilePath)]
pub github_output: Option<PathBuf>,
}
#[derive(Args)]
pub struct PublishArgs {
#[arg(short = 'c', long, default_value = crate::config::FILE, value_hint = clap::ValueHint::FilePath)]
pub config: PathBuf,
#[command(flatten)]
pub dry: DryRunArgs,
}
#[derive(Args)]
pub struct AssetsArgs {
#[arg(short = 'c', long, default_value = crate::config::FILE, value_hint = clap::ValueHint::FilePath)]
pub config: PathBuf,
#[arg(long)]
pub build: Option<String>,
#[arg(long)]
pub upload: bool,
#[arg(long)]
pub tag: Option<String>,
#[arg(long, value_hint = clap::ValueHint::FilePath)]
pub github_output: Option<PathBuf>,
}
impl PublishArgs {
#[must_use]
pub fn dry_run(&self) -> bool {
self.dry.dry_run
}
}
#[derive(Args)]
pub struct StatusArgs {
#[arg(short = 'd', long, default_value = ".changeset", value_hint = clap::ValueHint::DirPath)]
pub dir: PathBuf,
}
#[derive(Args)]
pub struct CheckArgs {
#[arg(short = 'd', long, default_value = ".changeset", value_hint = clap::ValueHint::DirPath)]
pub dir: PathBuf,
#[arg(short = 'c', long, default_value = crate::config::FILE, value_hint = clap::ValueHint::FilePath)]
pub config: PathBuf,
#[arg(long)]
pub versions: bool,
}
#[derive(Args)]
pub struct PrepareArgs {
#[arg(short = 'd', long, default_value = ".changeset", value_hint = clap::ValueHint::DirPath)]
pub dir: PathBuf,
#[arg(short = 'c', long, default_value = crate::config::FILE, value_hint = clap::ValueHint::FilePath)]
pub config: PathBuf,
#[command(flatten)]
pub dry: DryRunArgs,
#[arg(long, overrides_with = "no_pr")]
pub pr: bool,
#[arg(long, overrides_with = "pr")]
pub no_pr: bool,
}
impl PrepareArgs {
#[must_use]
pub fn open_pr(&self) -> bool {
self.pr
}
#[must_use]
pub fn dry_run(&self) -> bool {
self.dry.dry_run
}
}
#[cfg(test)]
mod tests {
use clap::Parser;
use super::{Cli, Command};
fn prepare_from(args: &[&str]) -> super::PrepareArgs {
let mut words = vec!["verctl", "prepare"];
words.extend_from_slice(args);
match Cli::try_parse_from(words).expect("parse") {
Cli {
command: Command::Prepare(args),
..
} => args,
_ => panic!("expected prepare"),
}
}
#[test]
fn prepare_defaults_to_local() {
let args = prepare_from(&[]);
assert!(!args.open_pr());
assert!(!args.no_pr);
assert!(!args.pr);
}
#[test]
fn prepare_no_pr_is_local() {
let args = prepare_from(&["--no-pr"]);
assert!(!args.open_pr());
assert!(args.no_pr);
}
#[test]
fn prepare_pr_is_settable() {
let args = prepare_from(&["--pr"]);
assert!(args.open_pr());
assert!(args.pr);
assert!(!args.no_pr);
}
#[test]
fn last_pr_flag_wins() {
assert!(!prepare_from(&["--pr", "--no-pr"]).open_pr());
assert!(prepare_from(&["--no-pr", "--pr"]).open_pr());
}
#[test]
fn check_versions_flag_parses() {
let cli = Cli::try_parse_from(["verctl", "check", "--versions"]).expect("parse");
let Command::Check(args) = cli.command else {
panic!("expected check");
};
assert!(args.versions);
}
#[test]
fn preview_is_dry_run() {
assert!(prepare_from(&["--preview"]).dry_run());
assert!(prepare_from(&["--dry-run"]).dry_run());
assert!(prepare_from(&["-n"]).dry_run());
}
#[test]
fn format_json_works_before_and_after_the_verb() {
for args in [
["verctl", "--format", "json", "pin"].as_slice(),
["verctl", "pin", "--format", "json"].as_slice(),
["verctl", "status", "--format", "json"].as_slice(),
["verctl", "-f", "json", "pin"].as_slice(),
] {
let cli = Cli::try_parse_from(args).unwrap_or_else(|e| panic!("{args:?}: {e}"));
assert!(cli.format.format.is_json(), "{args:?}");
}
}
}