use clap::{Parser, Subcommand}; use fds_common::Result; use std::{path::PathBuf, process::ExitCode}; #[derive(Parser)] #[command( name = "fds-cartridge", version = env!("FDS_BUILD_VERSION"), about = "Build Void source packages and ready-to-run cartridge images on Linux" )] struct Cli { /// Optional wrapper that accepts TOOL followed by its arguments. #[arg(long, global = true)] image_tool_runner: Option, #[command(flatten)] build: fds_workstation::software::BuildOptions, #[command(subcommand)] command: Action, } #[derive(Subcommand)] enum Action { /// Check EROFS tools, the inspection sandbox and legacy xz reader. Doctor, /// Build or package software on the workstation, never on the Pi. Software { #[command(subcommand)] command: Software, }, /// Construct and verify a complete 1+m GPT cartridge image before burning. Create { recipe: PathBuf, output: PathBuf }, /// Verify GPT, every filesystem, catalogue and installed program tree. Inspect { image: PathBuf }, /// Verify a prepared image and save an image/target-bound write preview. Preview { image: PathBuf, target: PathBuf, output: PathBuf, /// Use an existing disposable regular file instead of a physical USB drive. #[arg(long)] file_target: bool, }, /// Write the entire prepared image only after exact preview confirmation. Write { preview: PathBuf, #[arg(long)] confirm: String, }, } #[derive(Subcommand)] enum Software { /// Build a Void source template and install its complete runtime package tree. Build { recipe: PathBuf, output: PathBuf }, /// Check installed package metadata and every program/dependency file. Inspect { directory: PathBuf }, } fn run() -> Result<()> { let cli = Cli::parse(); let result = match cli.command { Action::Doctor => Ok(fds_workstation::doctor::cartridge( cli.image_tool_runner.as_deref(), )?), Action::Preview { image, target, output, file_target, } => serde_json::to_value(fds_workstation::writing::preview( &image, &target, &output, file_target, cli.image_tool_runner.as_deref(), )?), Action::Write { preview, confirm } => { fds_workstation::writing::write(&preview, &confirm)?; return Ok(()); } Action::Software { command } => serde_json::to_value(match command { Software::Build { recipe, output } => { fds_workstation::software::build(&recipe, &output, &cli.build)? } Software::Inspect { directory } => fds_workstation::software::load(&directory)?, }), Action::Create { recipe, output } => { serde_json::to_value(fds_workstation::cartridge::create( &recipe, &output, cli.image_tool_runner.as_deref(), &cli.build, )?) } Action::Inspect { image } => serde_json::to_value(fds_workstation::cartridge::inspect( &image, cli.image_tool_runner.as_deref(), )?), } .map_err(|e| fds_common::Error(e.to_string()))?; println!("{}", serde_json::to_string_pretty(&result).unwrap()); Ok(()) } fn main() -> ExitCode { match run() { Ok(()) => ExitCode::SUCCESS, Err(e) => { eprintln!("fds-cartridge: {e}"); ExitCode::from(2) } } } #[cfg(test)] mod tests { use super::*; use clap::CommandFactory; #[test] fn typed_grammar() { Cli::command().debug_assert(); assert!( Cli::try_parse_from([ "fds-cartridge", "software", "build", "recipe.toml", "bundle" ]) .is_ok() ); assert!( Cli::try_parse_from([ "fds-cartridge", "create", "recipe.toml", "card.img", "--force" ]) .is_err() ); assert!(Cli::try_parse_from(["fds-cartridge", "create", "recipe.toml"]).is_err()); } }