numi 0.2.2

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

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

#[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",
        value_enum,
        value_name = "MODE",
        help = "Control generation cache behavior for this run"
    )]
    pub incremental: Option<IncrementalMode>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum IncrementalMode {
    Auto,
    Always,
    Never,
    Refresh,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct IncrementalResolution {
    pub incremental: Option<bool>,
    pub parse_cache: Option<bool>,
    pub force_regenerate: bool,
}

impl IncrementalOverrideArgs {
    pub fn resolve(&self) -> IncrementalResolution {
        match self.incremental {
            Some(IncrementalMode::Auto) | None => IncrementalResolution::default(),
            Some(IncrementalMode::Always) => IncrementalResolution {
                incremental: Some(true),
                parse_cache: Some(true),
                force_regenerate: false,
            },
            Some(IncrementalMode::Never) => IncrementalResolution {
                incremental: Some(false),
                parse_cache: Some(false),
                force_regenerate: false,
            },
            Some(IncrementalMode::Refresh) => IncrementalResolution {
                incremental: Some(true),
                parse_cache: Some(true),
                force_regenerate: true,
            },
        }
    }
}