numi 0.1.0

CLI for generating Swift code from Apple project resources.
Documentation
use std::path::PathBuf;

use clap::{ArgAction, Args, Parser, Subcommand};

#[derive(Debug, Parser)]
#[command(
    name = "numi",
    version,
    about = "Generate Swift code from Apple project resources",
    long_about = "Generate Swift code from asset catalogs, localization files, and other project resources.",
    before_help = "Generate Swift code from Apple project resources",
    after_help = "Examples:\n  numi init\n  numi generate\n  numi check\n  numi generate --workspace\n  numi dump-context --job l10n",
    propagate_version = true,
    subcommand_required = true
)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Option<Command>,
}

#[derive(Debug, Subcommand)]
pub enum Command {
    #[command(
        about = "Generate outputs for one config or workspace",
        after_help = "Examples:\n  numi generate\n  numi generate --job assets --job l10n\n  numi generate --workspace"
    )]
    Generate(GenerateArgs),
    #[command(
        about = "Check whether generated outputs are up to date",
        after_help = "Examples:\n  numi check\n  numi check --job l10n\n  numi check --workspace"
    )]
    Check(CheckArgs),
    #[command(about = "Write a starter numi.toml in the current directory")]
    Init(InitArgs),
    #[command(about = "Inspect resolved config paths and values")]
    Config(ConfigCommand),
    #[command(
        name = "dump-context",
        about = "Print the template context for a single job",
        after_help = "Examples:\n  numi dump-context --job l10n\n  numi dump-context --config AppUI/numi.toml --job assets"
    )]
    DumpContext(DumpContextArgs),
}

#[derive(Debug, Args)]
#[command(about = "Inspect resolved config paths and values")]
pub struct ConfigCommand {
    #[command(subcommand)]
    pub command: ConfigSubcommand,
}

#[derive(Debug, Subcommand)]
pub enum ConfigSubcommand {
    #[command(about = "Print the resolved config path")]
    Locate(LocateArgs),
    #[command(about = "Print the resolved config with defaults applied")]
    Print(PrintArgs),
}

#[derive(Debug, Args)]
pub struct LocateArgs {
    #[arg(
        long = "config",
        help = "Use a specific numi.toml instead of auto-discovery"
    )]
    pub config: Option<PathBuf>,
}

#[derive(Debug, Args)]
pub struct GenerateArgs {
    #[arg(
        long = "config",
        help = "Use a specific numi.toml instead of auto-discovery"
    )]
    pub config: Option<PathBuf>,
    #[arg(
        long = "workspace",
        action = ArgAction::SetTrue,
        help = "Use the ancestor workspace manifest instead of the nearest member manifest"
    )]
    pub workspace: bool,
    #[arg(long = "job", help = "Limit generation to the selected job name")]
    pub jobs: Vec<String>,
    #[command(flatten)]
    pub incremental_override: IncrementalOverrideArgs,
}

#[derive(Debug, Args)]
pub struct CheckArgs {
    #[arg(
        long = "config",
        help = "Use a specific numi.toml instead of auto-discovery"
    )]
    pub config: Option<PathBuf>,
    #[arg(
        long = "workspace",
        action = ArgAction::SetTrue,
        help = "Use the ancestor workspace manifest instead of the nearest member manifest"
    )]
    pub workspace: bool,
    #[arg(long = "job", help = "Limit checking to the selected job name")]
    pub jobs: Vec<String>,
}

#[derive(Debug, Args)]
pub struct InitArgs {
    #[arg(
        long,
        help = "Overwrite an existing numi.toml in the current directory"
    )]
    pub force: bool,
}

#[derive(Debug, Args)]
pub struct PrintArgs {
    #[arg(
        long = "config",
        help = "Use a specific numi.toml instead of auto-discovery"
    )]
    pub config: Option<PathBuf>,
}

#[derive(Debug, Args)]
pub struct DumpContextArgs {
    #[arg(
        long = "config",
        help = "Use a specific numi.toml instead of auto-discovery"
    )]
    pub config: Option<PathBuf>,
    #[arg(long = "job", help = "Job name to render as JSON context")]
    pub job: String,
}

#[derive(Debug, Args, Default, Clone, PartialEq, Eq)]
pub struct IncrementalOverrideArgs {
    #[arg(
        long = "incremental",
        action = ArgAction::SetTrue,
        help = "Force incremental parsing when supported",
        conflicts_with = "no_incremental"
    )]
    pub incremental: bool,
    #[arg(
        long = "no-incremental",
        action = ArgAction::SetTrue,
        help = "Disable incremental parsing even when the config enables it"
    )]
    pub no_incremental: bool,
}

impl IncrementalOverrideArgs {
    pub fn resolve(&self) -> Option<bool> {
        if self.incremental {
            Some(true)
        } else if self.no_incremental {
            Some(false)
        } else {
            None
        }
    }
}