use std::ffi::OsString;
use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
#[derive(Debug, Parser)]
#[command(name = "stow", version, disable_help_subcommand = true)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Check(CargoCommandArgs),
Build(CargoCommandArgs),
Test(CargoCommandArgs),
Predict(CargoCommandArgs),
Setup(SetupArgs),
Status,
Stats(StatsArgs),
Clean,
CheckArtifact(CheckArtifactArgs),
FetchArtifact(FetchArtifactArgs),
Index(IndexArgs),
#[command(name = "rustc", hide = true)]
Rustc(WrapperCommandArgs),
#[command(name = "cc", hide = true)]
Cc(WrapperCommandArgs),
#[command(name = "__purge-cache-dir", hide = true)]
PurgeCacheDir(PurgeCacheDirArgs),
}
#[derive(Debug, Clone, Args)]
pub struct CargoCommandArgs {
#[arg(long)]
pub silent_compatible_upgrades: bool,
#[arg(long)]
pub no_stow_resolver: bool,
#[arg(
value_name = "CARGO_ARGS",
num_args = 0..,
trailing_var_arg = true,
allow_hyphen_values = true
)]
pub cargo_args: Vec<OsString>,
}
#[derive(Debug, Clone, Args)]
pub struct SetupArgs {
#[arg(long)]
pub github_env: bool,
}
#[derive(Debug, Clone, Args)]
pub struct StatsArgs {
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Clone, Args)]
pub struct CheckArtifactArgs {
pub target: String,
pub rustc_version: String,
pub c_metadata: String,
}
#[derive(Debug, Clone, Args)]
pub struct FetchArtifactArgs {
pub target: String,
pub rustc_version: String,
pub c_metadata: String,
pub output_path: PathBuf,
pub crate_name: String,
}
#[derive(Debug, Clone, Args)]
pub struct WrapperCommandArgs {
pub executable: OsString,
#[arg(
value_name = "WRAPPED_ARGS",
num_args = 0..,
trailing_var_arg = true,
allow_hyphen_values = true
)]
pub wrapped_args: Vec<OsString>,
}
#[derive(Debug, Clone, Args)]
pub struct PurgeCacheDirArgs {
#[arg(value_name = "PATH", num_args = 1..)]
pub paths: Vec<PathBuf>,
}
#[derive(Debug, Clone, Args)]
pub struct IndexArgs {
#[command(subcommand)]
pub command: IndexCommand,
}
#[derive(Debug, Clone, Subcommand)]
pub enum IndexCommand {
Refresh(IndexRefreshArgs),
Status,
}
#[derive(Debug, Clone, Args)]
pub struct IndexRefreshArgs {
#[arg(long)]
pub target: Option<String>,
#[arg(long)]
pub rustc_version: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_check_command_with_passthrough_cargo_args() {
let cli = Cli::try_parse_from([
"stow",
"check",
"--silent-compatible-upgrades",
"--target",
"aarch64-apple-darwin",
"--manifest-path",
"Cargo.toml",
])
.expect("parse check command");
let Command::Check(args) = cli.command else {
panic!("expected check command");
};
assert!(args.silent_compatible_upgrades);
assert_eq!(
args.cargo_args,
vec![
OsString::from("--target"),
OsString::from("aarch64-apple-darwin"),
OsString::from("--manifest-path"),
OsString::from("Cargo.toml"),
]
);
}
#[test]
fn parses_fetch_artifact_command() {
let cli = Cli::try_parse_from([
"stow",
"fetch-artifact",
"aarch64-apple-darwin",
"1.91.1",
"abc123",
"/tmp/out.tar",
"aho-corasick",
])
.expect("parse fetch-artifact command");
let Command::FetchArtifact(args) = cli.command else {
panic!("expected fetch-artifact command");
};
assert_eq!(args.target, "aarch64-apple-darwin");
assert_eq!(args.rustc_version, "1.91.1");
assert_eq!(args.c_metadata, "abc123");
assert_eq!(args.output_path, PathBuf::from("/tmp/out.tar"));
assert_eq!(args.crate_name, "aho-corasick");
}
#[test]
fn parses_setup_command_with_github_env_flag() {
let cli =
Cli::try_parse_from(["stow", "setup", "--github-env"]).expect("parse setup command");
let Command::Setup(args) = cli.command else {
panic!("expected setup command");
};
assert!(args.github_env);
}
#[test]
fn parses_index_refresh_command() {
let cli =
Cli::try_parse_from(["stow", "index", "refresh"]).expect("parse index refresh command");
let Command::Index(args) = cli.command else {
panic!("expected index command");
};
let IndexCommand::Refresh(args) = args.command else {
panic!("expected index refresh");
};
assert!(args.target.is_none());
assert!(args.rustc_version.is_none());
}
#[test]
fn parses_index_refresh_with_toolchain_overrides() {
let cli = Cli::try_parse_from([
"stow",
"index",
"refresh",
"--target",
"x86_64-unknown-linux-gnu",
"--rustc-version",
"1.91.1",
])
.expect("parse index refresh command");
let Command::Index(args) = cli.command else {
panic!("expected index command");
};
let IndexCommand::Refresh(args) = args.command else {
panic!("expected index refresh");
};
assert_eq!(args.target.as_deref(), Some("x86_64-unknown-linux-gnu"));
assert_eq!(args.rustc_version.as_deref(), Some("1.91.1"));
}
#[test]
fn parses_index_status_command() {
let cli =
Cli::try_parse_from(["stow", "index", "status"]).expect("parse index status command");
let Command::Index(args) = cli.command else {
panic!("expected index command");
};
assert!(matches!(args.command, IndexCommand::Status));
}
#[test]
fn parses_hidden_rustc_wrapper_command() {
let cli = Cli::try_parse_from(["stow", "rustc", "/usr/bin/rustc", "--crate-name", "itoa"])
.expect("parse rustc wrapper command");
let Command::Rustc(args) = cli.command else {
panic!("expected rustc command");
};
assert_eq!(args.executable, OsString::from("/usr/bin/rustc"));
assert_eq!(
args.wrapped_args,
vec![OsString::from("--crate-name"), OsString::from("itoa")]
);
}
}