use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum};
#[derive(Parser)]
#[command(name = "callisto", version)]
pub struct Cli {
#[command(flatten)]
pub global: GlobalArgs,
#[command(subcommand)]
pub command: Command,
}
#[derive(Args, Clone, Debug)]
pub struct GlobalArgs {
#[arg(long, global = true, value_enum, default_value = "text")]
pub format: OutputFormat,
#[arg(long, global = true, default_value = ".")]
pub cwd: PathBuf,
#[arg(
long,
global = true,
help = "Preview manifest and file changes without writing to disk"
)]
pub dry_run: bool,
}
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputFormat {
Text,
Json,
}
#[derive(Subcommand, Clone, Debug)]
pub enum Command {
Add(AddArgs),
Status(StatusArgs),
Matrix(MatrixArgs),
Version(VersionArgs),
#[command(subcommand)]
Pre(PreArgs),
Validate(ValidateArgs),
Snapshot(SnapshotArgs),
Init(InitArgs),
PlanPublish(PlanPublishArgs),
Publish(PublishArgs),
ComposePrBody(ComposePrBodyArgs),
Tag(TagArgs),
Completions(CompletionsArgs),
Schema(SchemaArgs),
}
#[derive(Args, Clone, Debug, Default)]
pub struct SchemaArgs {
#[arg(long = "type", value_name = "TYPE")]
pub target_type: Option<String>,
}
#[derive(Args, Clone, Debug)]
pub struct AddArgs {
#[arg(long = "package", value_name = "NAME:SEVERITY")]
pub packages: Vec<String>,
#[arg(long)]
pub summary: Option<String>,
}
#[derive(Args, Clone, Debug)]
pub struct StatusArgs {
#[arg(long)]
pub strict: bool,
#[arg(long)]
pub strict_graph: bool,
#[arg(long)]
pub check: bool,
}
#[derive(Args, Clone, Debug, Default)]
pub struct MatrixArgs {
#[arg(long)]
pub package: Option<String>,
}
#[derive(Args, Clone, Debug)]
pub struct VersionArgs {
#[arg(long)]
pub refresh_lockfiles: bool,
#[arg(long)]
pub strict: bool,
#[arg(long)]
pub strict_graph: bool,
#[arg(long)]
pub allow_empty_changesets: bool,
}
#[derive(Subcommand, Clone, Debug)]
pub enum PreArgs {
Enter { tag: String },
Exit,
}
#[derive(Args, Clone, Debug)]
pub struct ValidateArgs {
#[arg(long)]
pub staged: bool,
#[arg(long, value_name = "REF", conflicts_with = "staged")]
pub since: Option<String>,
#[arg(long)]
pub strict: bool,
#[arg(long)]
pub strict_graph: bool,
}
#[derive(Args, Clone, Debug)]
pub struct SnapshotArgs {
#[arg(long)]
pub tag: String,
#[arg(long)]
pub strict: bool,
}
#[derive(Args, Clone, Debug)]
pub struct InitArgs {
#[arg(long)]
pub yes: bool,
}
#[derive(Args, Clone, Debug, Default)]
pub struct PlanPublishArgs {
#[arg(long = "package", value_name = "NAME")]
pub only: Vec<String>,
}
#[derive(Args, Clone, Debug, Default)]
pub struct PublishArgs {
#[arg(long = "package", value_name = "NAME")]
pub only: Vec<String>,
}
#[derive(Args, Clone, Debug)]
pub struct ComposePrBodyArgs {
#[arg(long, value_name = "TEXT|-")]
pub existing_body: Option<String>,
#[arg(long = "label")]
pub labels: Vec<String>,
#[arg(long)]
pub branch: Option<String>,
}
#[derive(Args, Clone, Debug)]
pub struct TagArgs {
#[arg(long, value_name = "FILE|-")]
pub plan: String,
#[arg(long)]
pub floating_major: bool,
#[arg(long)]
pub strict: bool,
}
#[derive(Args, Clone, Debug)]
pub struct CompletionsArgs {
#[arg(value_enum)]
pub shell: clap_complete::Shell,
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn strict_flag_help_text_is_meaningful() {
let mut cmd = Cli::command();
cmd.build();
let status_sub = cmd
.get_subcommands()
.find(|s| s.get_name() == "status")
.expect("status subcommand must exist");
let strict_arg = status_sub
.get_arguments()
.find(|a| a.get_long() == Some("strict"))
.expect("--strict argument must exist on status subcommand");
let help = strict_arg
.get_help()
.map(|h| h.to_string())
.unwrap_or_default()
.to_lowercase();
assert!(
help.contains("strict"),
"--strict help text must contain the word 'strict'; got: {help:?}"
);
assert!(
help.contains("warning") || help.contains("error"),
"--strict help text must mention 'warning' or 'error'; got: {help:?}"
);
assert!(
help.len() > 20,
"--strict help text is too short to be meaningful: {help:?}"
);
}
#[test]
fn test_cli_parse_matrix_command_with_package() {
use clap::Parser;
let cli = Cli::parse_from(["callisto", "matrix", "--package", "foo"]);
if let Command::Matrix(args) = cli.command {
assert_eq!(args.package, Some("foo".to_string()));
} else {
panic!("Expected Matrix command");
}
}
#[test]
fn test_cli_parse_matrix_command_bare() {
use clap::Parser;
let cli = Cli::parse_from(["callisto", "matrix"]);
if let Command::Matrix(args) = cli.command {
assert_eq!(args.package, None);
} else {
panic!("Expected Matrix command");
}
}
}