fast-yaml-cli 0.6.5

Fast YAML command-line processor with validation and linting
Documentation
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;

/// Fast YAML processor with validation and linting
#[derive(Parser, Debug)]
#[command(
    name = "fy",
    about = "Fast YAML processor with validation and linting",
    version,
    author,
    long_about = None
)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Option<Command>,

    /// Edit file in-place (requires file argument)
    #[arg(short = 'i', long, global = true)]
    pub in_place: bool,

    /// Output file (default: stdout)
    #[arg(short, long, global = true, value_name = "FILE")]
    pub output: Option<PathBuf>,

    /// Output format
    #[arg(short = 'f', long, value_enum, default_value = "yaml")]
    pub format: OutputFormat,

    /// Disable colored output
    #[arg(long, global = true)]
    pub no_color: bool,

    /// Quiet mode (errors only)
    #[arg(short, long, global = true)]
    pub quiet: bool,

    /// Verbose output
    #[arg(short, long, global = true)]
    pub verbose: bool,
}

#[derive(Subcommand, Debug)]
pub enum Command {
    /// Parse and validate YAML
    Parse {
        /// Input file (default: stdin)
        file: Option<PathBuf>,

        /// Show parse statistics
        #[arg(long)]
        stats: bool,
    },

    /// Format YAML with consistent style
    Format {
        /// Input paths (files, directories, or glob patterns).
        /// If empty and no --stdin-files, reads from stdin
        #[arg(value_name = "PATHS")]
        paths: Vec<PathBuf>,

        /// Indentation width (2-8 spaces)
        #[arg(long, default_value = "2", value_parser = clap::value_parser!(u8).range(2..=8))]
        indent: u8,

        /// Maximum line width
        #[arg(long, default_value = "80")]
        width: usize,

        /// Number of parallel jobs (0 = auto-detect)
        #[arg(short = 'j', long, default_value = "0")]
        jobs: usize,

        /// Read file paths from stdin (one per line)
        #[arg(long, conflicts_with = "paths")]
        stdin_files: bool,

        /// Include files matching glob pattern (can be repeated)
        #[arg(long)]
        include: Vec<String>,

        /// Exclude files matching glob pattern (can be repeated)
        #[arg(long)]
        exclude: Vec<String>,

        /// Don't recurse into subdirectories
        #[arg(long)]
        no_recursive: bool,

        /// Show what would be changed without modifying files
        #[arg(short = 'n', long)]
        dry_run: bool,

        /// Suppress the error when YAML comments are detected.
        /// Comments are not preserved by the formatter and will be stripped.
        /// Without this flag, formatting a file that contains comments exits with an error.
        #[arg(long)]
        strip_comments: bool,
    },

    /// Convert between YAML and JSON
    Convert {
        /// Target format
        #[arg(value_enum)]
        to: ConvertFormat,

        /// Input file (default: stdin)
        file: Option<PathBuf>,

        /// Pretty-print JSON output
        #[arg(long, default_value_t = true, num_args = 0..=1, default_missing_value = "true", action = clap::ArgAction::Set)]
        pretty: bool,
    },

    #[cfg(feature = "linter")]
    /// Lint YAML with diagnostics
    Lint {
        /// Input paths (files, directories, or glob patterns).
        /// If empty, reads from stdin.
        #[arg(value_name = "PATHS")]
        paths: Vec<PathBuf>,

        /// Path to config file (default: auto-discover .fast-yaml.yaml)
        #[arg(long, value_name = "FILE", conflicts_with = "no_config")]
        config: Option<PathBuf>,

        /// Disable config file auto-discovery
        #[arg(long, conflicts_with = "config")]
        no_config: bool,

        /// Maximum line length (overrides config file)
        #[arg(long)]
        max_line_length: Option<usize>,

        /// Indentation size (overrides config file)
        #[arg(long)]
        indent_size: Option<usize>,

        /// Lint output format
        #[arg(long, value_enum, default_value = "text")]
        format: LintFormat,

        /// Allow duplicate keys — overrides config file (opt-in, suppresses duplicate key errors)
        #[arg(long, num_args = 0..=1, default_missing_value = "true", action = clap::ArgAction::Set)]
        allow_duplicate_keys: Option<bool>,

        /// Include files matching glob pattern (can be repeated)
        #[arg(long)]
        include: Vec<String>,

        /// Exclude files matching glob pattern (can be repeated)
        #[arg(long)]
        exclude: Vec<String>,

        /// Don't recurse into subdirectories
        #[arg(long)]
        no_recursive: bool,

        /// Number of parallel jobs (0 = auto-detect)
        #[arg(short = 'j', long, default_value = "0")]
        jobs: usize,
    },
}

#[derive(ValueEnum, Clone, Debug)]
pub enum OutputFormat {
    Yaml,
    Json,
    Compact,
}

#[derive(ValueEnum, Clone, Debug)]
pub enum ConvertFormat {
    Yaml,
    Json,
}

#[cfg(feature = "linter")]
#[derive(ValueEnum, Clone, Debug)]
pub enum LintFormat {
    Text,
    Json,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn verify_cli() {
        use clap::CommandFactory;
        Cli::command().debug_assert();
    }
}