|
| 1 | +//! Command line interface definition using clap. |
| 2 | +//! |
| 3 | +//! This module defines the [`Cli`] structure and its subcommands. |
| 4 | +//! It mirrors the design described in `docs/netsuke-design.md`. |
| 5 | +
|
| 6 | +use clap::{Parser, Subcommand}; |
| 7 | +use std::path::PathBuf; |
| 8 | + |
| 9 | +/// Maximum number of jobs accepted by the CLI. |
| 10 | +const MAX_JOBS: usize = 64; |
| 11 | + |
| 12 | +fn parse_jobs(s: &str) -> Result<usize, String> { |
| 13 | + let value: usize = s |
| 14 | + .parse() |
| 15 | + .map_err(|_| format!("{s} is not a valid number"))?; |
| 16 | + if (1..=MAX_JOBS).contains(&value) { |
| 17 | + Ok(value) |
| 18 | + } else { |
| 19 | + Err(format!("jobs must be between 1 and {MAX_JOBS}")) |
| 20 | + } |
| 21 | +} |
| 22 | + |
| 23 | +/// A modern, friendly build system that uses YAML and Jinja, powered by Ninja. |
| 24 | +#[derive(Debug, Parser)] |
| 25 | +#[command(author, version, about, long_about = None)] |
| 26 | +pub struct Cli { |
| 27 | + /// Path to the Netsuke manifest file to use. |
| 28 | + #[arg(short, long, value_name = "FILE", default_value = "Netsukefile")] |
| 29 | + pub file: PathBuf, |
| 30 | + |
| 31 | + /// Change to this directory before doing anything. |
| 32 | + #[arg(short = 'C', long, value_name = "DIR")] |
| 33 | + pub directory: Option<PathBuf>, |
| 34 | + |
| 35 | + /// Set the number of parallel build jobs. |
| 36 | + #[arg(short, long, value_name = "N", value_parser = parse_jobs)] |
| 37 | + pub jobs: Option<usize>, |
| 38 | + |
| 39 | + #[command(subcommand)] |
| 40 | + pub command: Option<Commands>, |
| 41 | +} |
| 42 | + |
| 43 | +impl Cli { |
| 44 | + /// Parse command-line arguments, providing `build` as the default command. |
| 45 | + #[must_use] |
| 46 | + pub fn parse_with_default() -> Self { |
| 47 | + Self::parse().with_default_command() |
| 48 | + } |
| 49 | + |
| 50 | + /// Parse the provided arguments, applying the default command when needed. |
| 51 | + /// |
| 52 | + /// # Panics |
| 53 | + /// |
| 54 | + /// Panics if argument parsing fails. |
| 55 | + #[must_use] |
| 56 | + pub fn parse_from_with_default<I, T>(args: I) -> Self |
| 57 | + where |
| 58 | + I: IntoIterator<Item = T>, |
| 59 | + T: Into<std::ffi::OsString> + Clone, |
| 60 | + { |
| 61 | + Self::try_parse_from(args) |
| 62 | + .unwrap_or_else(|e| panic!("CLI parsing failed: {e}")) |
| 63 | + .with_default_command() |
| 64 | + } |
| 65 | + |
| 66 | + /// Apply the default command if none was specified. |
| 67 | + #[must_use] |
| 68 | + fn with_default_command(mut self) -> Self { |
| 69 | + if self.command.is_none() { |
| 70 | + self.command = Some(Commands::Build { |
| 71 | + targets: Vec::new(), |
| 72 | + }); |
| 73 | + } |
| 74 | + self |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +/// Available top-level commands for Netsuke. |
| 79 | +#[derive(Debug, Subcommand, PartialEq, Eq, Clone)] |
| 80 | +pub enum Commands { |
| 81 | + /// Build specified targets (or default targets if none are given) [default]. |
| 82 | + Build { |
| 83 | + /// A list of specific targets to build. |
| 84 | + targets: Vec<String>, |
| 85 | + }, |
| 86 | + |
| 87 | + /// Remove build artifacts and intermediate files. |
| 88 | + Clean, |
| 89 | + |
| 90 | + /// Display the build dependency graph in DOT format for visualization. |
| 91 | + Graph, |
| 92 | +} |
0 commit comments