use clap::{Args, Parser, Subcommand};
#[derive(Parser, Debug, Clone)]
#[command(
name = "kaze",
about = "Zephyr build system companion.",
version,
propagate_version = true
)]
pub struct Cli {
#[arg(short = 'c', long = "clean")]
pub preclean: bool,
#[arg(short = 'a', long = "all")]
pub all: bool,
#[arg(short = 'p', long = "profile")]
pub profile: Option<String>,
#[arg(short = 'b', long = "board")]
pub board: Option<String>,
#[arg(short = 'r', long = "runner")]
pub runner: Option<String>,
#[arg(long = "project")]
pub project: Option<std::path::PathBuf>,
#[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(short = 'd', long = "dry-run")]
pub dry_run: bool,
#[command(subcommand)]
pub command: Command,
}
impl Cli {
#[must_use]
pub fn parse_args() -> Self {
Self::parse()
}
}
#[derive(Subcommand, Debug, Clone)]
pub enum Command {
#[command(alias = "i")]
Init,
#[command(alias = "bo")]
Boards,
#[command(alias = "rn")]
Runners,
#[command(alias = "p")]
Profiles,
#[command(alias = "c")]
Clean,
#[command(alias = "cf")]
Conf(PhaseArgs),
#[command(alias = "b")]
Build(PhaseArgs),
#[command(alias = "r")]
Run(RunArgs),
#[command(alias = "f")]
Flash(FlashArgs),
}
#[derive(Args, Debug, Default, Clone)]
pub struct PhaseArgs {
#[arg(trailing_var_arg = true)]
pub extra: Vec<String>,
}
#[derive(Args, Debug, Default, Clone)]
pub struct SysbuildArgs {
#[arg(short = 'l', long = "list")]
pub list: bool,
#[arg(short = 'i', long = "image")]
pub image: Option<String>,
}
#[derive(Args, Debug, Default, Clone)]
pub struct RunArgs {
#[arg(short = 'n', long = "norebuild")]
pub norebuild: bool,
#[command(flatten)]
pub sys: SysbuildArgs,
#[command(flatten)]
pub phase: PhaseArgs,
}
#[derive(Args, Debug, Default, Clone)]
pub struct FlashArgs {
#[command(flatten)]
pub sys: SysbuildArgs,
#[command(flatten)]
pub phase: PhaseArgs,
}
#[cfg(test)]
mod tests {
use clap::Parser;
use super::*;
#[test]
fn parse_defaults() {
let cli = Cli::try_parse_from(["kaze", "clean"]).expect("parse ok");
assert!(!cli.preclean);
assert!(!cli.all);
assert_eq!(cli.profile, None);
assert_eq!(cli.board, None);
assert_eq!(cli.runner, None);
assert_eq!(cli.verbose, 0);
assert!(matches!(cli.command, Command::Clean));
}
#[test]
fn parse_flags_and_options() {
let cli = Cli::try_parse_from([
"kaze",
"-c",
"-a",
"-p",
"dev",
"-b",
"native_sim",
"-r",
"native",
"-v",
"-v",
"build",
])
.expect("parse ok");
assert!(cli.preclean);
assert!(cli.all);
assert_eq!(cli.profile.as_deref(), Some("dev"));
assert_eq!(cli.board.as_deref(), Some("native_sim"));
assert_eq!(cli.runner.as_deref(), Some("native"));
assert_eq!(cli.verbose, 2);
assert!(matches!(cli.command, Command::Build(_)));
}
#[test]
fn parse_subcommands_with_args() {
let cli = Cli::try_parse_from(["kaze", "run", "-n", "-l", "-i", "app", "--", "-DOPT=1"])
.expect("parse ok");
assert!(matches!(cli.command, Command::Run(_)));
let Command::Run(args) = cli.command else {
return;
};
assert!(args.norebuild);
assert!(args.sys.list);
assert_eq!(args.sys.image.as_deref(), Some("app"));
assert_eq!(args.phase.extra, vec!["-DOPT=1"]);
}
#[test]
fn parse_simple_subcommands() {
let cli_boards = Cli::try_parse_from(["kaze", "boards"]).expect("parse ok");
assert!(matches!(cli_boards.command, Command::Boards));
let cli_runners = Cli::try_parse_from(["kaze", "runners"]).expect("parse ok");
assert!(matches!(cli_runners.command, Command::Runners));
let cli_profiles = Cli::try_parse_from(["kaze", "profiles"]).expect("parse ok");
assert!(matches!(cli_profiles.command, Command::Profiles));
}
#[test]
fn parse_conf_and_flash_with_passthrough() {
let cli_conf = Cli::try_parse_from(["kaze", "conf", "--", "-DOPT=1"]).expect("parse ok");
assert!(matches!(cli_conf.command, Command::Conf(_)));
let Command::Conf(conf_args) = cli_conf.command else {
return;
};
assert_eq!(conf_args.extra, vec!["-DOPT=1"]);
let cli_flash =
Cli::try_parse_from(["kaze", "flash", "-l", "-i", "app"]).expect("parse ok");
assert!(matches!(cli_flash.command, Command::Flash(_)));
let Command::Flash(flash_args) = cli_flash.command else {
return;
};
assert!(flash_args.sys.list);
assert_eq!(flash_args.sys.image.as_deref(), Some("app"));
}
}