rcpufetch 0.0.5

[ALPHA] A rusty crossplatform, but simple CLI binutil for reading CPU information.
// Command Line Arguments Module
// This module handles parsing command line arguments for rcpufetch, mirroring the
// flag set of upstream cpufetch (see cpufetch/src/common/args.c) so existing muscle
// memory / scripts transfer directly.

use std::env;

/// Rendering style for the logo/text block (`-s`/`--style`), matching cpufetch's
/// `fancy` (default, full RGB truecolor), `retro` (colored glyphs, no solid blocks),
/// and `legacy` (no color at all, for terminals without truecolor support) styles.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Style {
    #[default]
    Fancy,
    Retro,
    Legacy,
}

impl Style {
    pub fn parse(s: &str) -> Option<Style> {
        match s.to_lowercase().as_str() {
            "fancy" => Some(Style::Fancy),
            "retro" => Some(Style::Retro),
            "legacy" => Some(Style::Legacy),
            _ => None,
        }
    }
}

/// Command line arguments structure. Holds all supported CLI options for rcpufetch.
#[derive(Debug, Default)]
pub struct Args {
    /// Disable logo display (`-n`/`--no-logo`)
    pub no_logo: bool,
    /// Override logo display with specific vendor (`-l`/`--logo <VENDOR>`)
    pub logo: Option<String>,
    /// Display license information (`--license`)
    pub license: bool,
    /// Display help information (`-h`/`--help`)
    pub help: bool,
    /// Display version information (`-V`/`--version`)
    pub version: bool,
    /// Generate shell completions (`--completions <SHELL>`)
    pub completions: Option<String>,

    // --- cpufetch-parity flags ---
    /// `-s`/`--style <fancy|retro|legacy>`
    pub style: Style,
    /// `-c`/`--color <scheme|R,G,B:R,G,B:R,G,B:R,G,B:R,G,B>`
    pub color: Option<String>,
    /// `-r`/`--raw` (x86 only): dump raw cpuid data instead of the normal report
    pub raw: bool,
    /// `-F`/`--full-cpu-name` (x86 only): print the unabbreviated brand string
    pub full_cpu_name: bool,
    /// `--logo-long`: force the large logo variant
    pub logo_long: bool,
    /// `--logo-short`: force the small logo variant
    pub logo_short: bool,
    /// `--logo-intel-new`: force the modern Intel logo
    pub logo_intel_new: bool,
    /// `--logo-intel-old`: force the classic Intel logo
    pub logo_intel_old: bool,
    /// `--accurate-pp` (x86+Linux): measure peak performance instead of estimating it
    pub accurate_pp: bool,
    /// `--measure-max-freq`: measure max frequency instead of reading it from the OS
    pub measure_max_freq: bool,
    /// `-d`/`--debug`: print low-level identifying register info (cpuid/MIDR/PVR) and exit
    pub debug: bool,
    /// `-v`/`--verbose`: print diagnostic warnings about how info was gathered
    pub verbose: bool,
}

impl Args {
    /// Parse command line arguments manually (no external dependency, for fast/simple builds).
    pub fn parse() -> Result<Self, String> {
        let args: Vec<String> = env::args().collect();
        let mut a = Args::default();
        let mut i = 1; // Skip program name

        while i < args.len() {
            match args[i].as_str() {
                "-h" | "--help" => a.help = true,
                "-V" | "--version" => a.version = true,
                "--license" => a.license = true,
                "-n" | "--no-logo" => a.no_logo = true,
                "-d" | "--debug" => a.debug = true,
                "-v" | "--verbose" => a.verbose = true,
                "-r" | "--raw" => a.raw = true,
                "-F" | "--full-cpu-name" => a.full_cpu_name = true,
                "--logo-long" => a.logo_long = true,
                "--logo-short" => a.logo_short = true,
                "--logo-intel-new" => a.logo_intel_new = true,
                "--logo-intel-old" => a.logo_intel_old = true,
                "--accurate-pp" => a.accurate_pp = true,
                "--measure-max-freq" => a.measure_max_freq = true,
                "-l" | "--logo" => {
                    i += 1;
                    if i >= args.len() {
                        return Err("Error: --logo requires a value".to_string());
                    }
                    a.logo = Some(args[i].clone());
                }
                arg if arg.starts_with("--logo=") => {
                    let value = arg.strip_prefix("--logo=").unwrap();
                    if value.is_empty() {
                        return Err("Error: --logo requires a value".to_string());
                    }
                    a.logo = Some(value.to_string());
                }
                "-s" | "--style" => {
                    i += 1;
                    if i >= args.len() {
                        return Err(
                            "Error: --style requires a value (fancy, retro, legacy)".to_string()
                        );
                    }
                    a.style = Style::parse(&args[i]).ok_or_else(|| {
                        format!(
                            "Error: invalid style '{}'. Valid styles: fancy, retro, legacy",
                            args[i]
                        )
                    })?;
                }
                arg if arg.starts_with("--style=") => {
                    let value = arg.strip_prefix("--style=").unwrap();
                    a.style = Style::parse(value).ok_or_else(|| {
                        format!(
                            "Error: invalid style '{}'. Valid styles: fancy, retro, legacy",
                            value
                        )
                    })?;
                }
                "-c" | "--color" => {
                    i += 1;
                    if i >= args.len() {
                        return Err("Error: --color requires a value".to_string());
                    }
                    if a.color.is_some() {
                        return Err("Error: --color specified twice".to_string());
                    }
                    a.color = Some(args[i].clone());
                }
                arg if arg.starts_with("--color=") => {
                    let value = arg.strip_prefix("--color=").unwrap();
                    if a.color.is_some() {
                        return Err("Error: --color specified twice".to_string());
                    }
                    a.color = Some(value.to_string());
                }
                "--completions" => {
                    i += 1;
                    if i >= args.len() {
                        return Err(
                            "Error: --completions requires a shell name (fish, bash, zsh)"
                                .to_string(),
                        );
                    }
                    a.completions = Some(args[i].clone());
                }
                arg => {
                    return Err(format!("Error: Unknown argument '{}'", arg));
                }
            }
            i += 1;
        }

        if a.logo_long && a.logo_short {
            eprintln!(
                "Warning: --logo-long and --logo-short are mutually exclusive; ignoring both"
            );
            a.logo_long = false;
            a.logo_short = false;
        }
        if a.logo_intel_new && a.logo_intel_old {
            eprintln!(
                "Warning: --logo-intel-new and --logo-intel-old are mutually exclusive; ignoring both"
            );
            a.logo_intel_new = false;
            a.logo_intel_old = false;
        }

        Ok(a)
    }
}

/// Print help information to stdout.
pub fn print_help() {
    println!("rcpufetch {}", env!("CARGO_PKG_VERSION"));
    println!("{}", env!("CARGO_PKG_DESCRIPTION"));
    println!();
    println!("USAGE:");
    println!("    rcpufetch [OPTIONS]");
    println!();
    println!("OPTIONS:");
    println!("    -h, --help                   Print help information");
    println!("    -V, --version                Print version information");
    println!("        --license                Display license information");
    println!("        --completions <SHELL>    Generate shell completions (fish, bash, zsh)");
    println!("    -n, --no-logo                Disable logo display");
    println!("    -l, --logo <VENDOR>          Override logo display with specific vendor");
    println!(
        "                                 Valid vendors: nvidia, powerpc, arm, amd, intel, apple"
    );
    println!("    -s, --style <STYLE>          Set output style: fancy (default), retro, legacy");
    println!("    -c, --color <SCHEME>         Set color scheme: intel, intel-new, amd, ibm, arm,");
    println!("                                 rockchip, sifive, or R,G,B:R,G,B:R,G,B:R,G,B:R,G,B");
    println!("    -r, --raw                    Dump raw cpuid data (x86 only)");
    println!("    -F, --full-cpu-name          Show the unabbreviated CPU name (x86 only)");
    println!("        --logo-long              Force the large logo variant");
    println!("        --logo-short             Force the small logo variant");
    println!("        --logo-intel-new         Force the modern Intel logo (x86/Intel only)");
    println!("        --logo-intel-old         Force the classic Intel logo (x86/Intel only)");
    println!(
        "        --accurate-pp            Measure peak performance instead of estimating it (x86+Linux)"
    );
    println!(
        "        --measure-max-freq       Measure max frequency instead of reading it from the OS"
    );
    println!("    -d, --debug                  Print low-level identifying register info and exit");
    println!("    -v, --verbose                Print diagnostic warnings about info gathering");
    println!();
    println!("EXAMPLES:");
    println!("    rcpufetch                    Display CPU info with auto-detected logo");
    println!("    rcpufetch --no-logo          Display CPU info without logo");
    println!("    rcpufetch --logo intel       Display CPU info with Intel logo");
    println!("    rcpufetch --style retro      Display CPU info in retro style");
    println!("    rcpufetch --license          Show license information");
}

/// Print version information to stdout.
pub fn print_version() {
    println!(
        "{} {} ({} {} build)",
        env!("CARGO_PKG_NAME"),
        env!("CARGO_PKG_VERSION"),
        std::env::consts::OS,
        std::env::consts::ARCH
    );
}

/// Print license information to stdout.
pub fn print_license() {
    println!("Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>");
    println!("Licensed under the GNU GPLv3: GNU General Public License version 3.");
    println!("rcpufetch comes with ABSOLUTELY NO WARRANTY.");
    println!();
    println!("A copy of the GNU General Public License Version 3 should");
    println!("have been provided with rcpufetch. If not, you can");
    println!("find it at: <https://www.gnu.org/licenses/gpl-3.0.html>.");
    println!();
    println!("This is free software, and you are welcome to redistribute it");
    println!(
        "under certain conditions, as described above. Type `rcpufetch --help` for assistance."
    );
}

/// Generate shell completions for the specified shell.
pub fn print_completions(shell: &str) {
    match shell.to_lowercase().as_str() {
        "fish" => print_fish_completions(),
        "bash" => print_bash_completions(),
        "zsh" => print_zsh_completions(),
        _ => {
            eprintln!(
                "Error: Unsupported shell '{}'. Supported shells: fish, bash, zsh",
                shell
            );
            std::process::exit(1);
        }
    }
}

const LONG_OPTS: &str = "-h --help -V --version --license -n --no-logo -l --logo -s --style -c --color -r --raw -F --full-cpu-name --logo-long --logo-short --logo-intel-new --logo-intel-old --accurate-pp --measure-max-freq -d --debug -v --verbose --completions";

fn print_fish_completions() {
    println!("# Fish completions for rcpufetch");
    println!("complete -c rcpufetch -s h -l help -d 'Print help information'");
    println!("complete -c rcpufetch -s V -l version -d 'Print version information'");
    println!("complete -c rcpufetch -l license -d 'Display license information'");
    println!("complete -c rcpufetch -s n -l no-logo -d 'Disable logo display'");
    println!(
        "complete -c rcpufetch -s l -l logo -x -a 'nvidia powerpc arm amd intel apple' -d 'Override logo display with specific vendor'"
    );
    println!(
        "complete -c rcpufetch -s s -l style -x -a 'fancy retro legacy' -d 'Set output style'"
    );
    println!(
        "complete -c rcpufetch -s c -l color -x -a 'intel intel-new amd ibm arm rockchip sifive' -d 'Set color scheme'"
    );
    println!("complete -c rcpufetch -s r -l raw -d 'Dump raw cpuid data (x86 only)'");
    println!("complete -c rcpufetch -s F -l full-cpu-name -d 'Show the unabbreviated CPU name'");
    println!("complete -c rcpufetch -l logo-long -d 'Force the large logo variant'");
    println!("complete -c rcpufetch -l logo-short -d 'Force the small logo variant'");
    println!("complete -c rcpufetch -l logo-intel-new -d 'Force the modern Intel logo'");
    println!("complete -c rcpufetch -l logo-intel-old -d 'Force the classic Intel logo'");
    println!(
        "complete -c rcpufetch -l accurate-pp -d 'Measure peak performance instead of estimating it'"
    );
    println!(
        "complete -c rcpufetch -l measure-max-freq -d 'Measure max frequency instead of reading it from the OS'"
    );
    println!(
        "complete -c rcpufetch -s d -l debug -d 'Print low-level identifying register info and exit'"
    );
    println!(
        "complete -c rcpufetch -s v -l verbose -d 'Print diagnostic warnings about info gathering'"
    );
    println!(
        "complete -c rcpufetch -l completions -x -a 'fish bash zsh' -d 'Generate shell completions'"
    );
}

fn print_bash_completions() {
    println!("# Bash completions for rcpufetch");
    println!("_rcpufetch() {{");
    println!("    local cur prev opts");
    println!("    COMPREPLY=()");
    println!("    cur=\"${{COMP_WORDS[COMP_CWORD]}}\"");
    println!("    prev=\"${{COMP_WORDS[COMP_CWORD-1]}}\"");
    println!("    opts=\"{}\"", LONG_OPTS);
    println!();
    println!("    case \"${{prev}}\" in");
    println!("        --logo|-l)");
    println!(
        "            COMPREPLY=($(compgen -W \"nvidia powerpc arm amd intel apple\" -- \"${{cur}}\"))"
    );
    println!("            return 0");
    println!("            ;;");
    println!("        --style|-s)");
    println!("            COMPREPLY=($(compgen -W \"fancy retro legacy\" -- \"${{cur}}\"))");
    println!("            return 0");
    println!("            ;;");
    println!("        --color|-c)");
    println!(
        "            COMPREPLY=($(compgen -W \"intel intel-new amd ibm arm rockchip sifive\" -- \"${{cur}}\"))"
    );
    println!("            return 0");
    println!("            ;;");
    println!("        --completions)");
    println!("            COMPREPLY=($(compgen -W \"fish bash zsh\" -- \"${{cur}}\"))");
    println!("            return 0");
    println!("            ;;");
    println!("    esac");
    println!();
    println!("    COMPREPLY=($(compgen -W \"${{opts}}\" -- \"${{cur}}\"))");
    println!("}}");
    println!("complete -F _rcpufetch rcpufetch");
}

fn print_zsh_completions() {
    println!("# Zsh completions for rcpufetch");
    println!("#compdef rcpufetch");
    println!();
    println!("_rcpufetch() {{");
    println!("    _arguments \\");
    println!("        '(-h --help){{-h,--help}}[Print help information]' \\");
    println!("        '(-V --version){{-V,--version}}[Print version information]' \\");
    println!("        '--license[Display license information]' \\");
    println!("        '(-n --no-logo){{-n,--no-logo}}[Disable logo display]' \\");
    println!(
        "        '(-l --logo){{-l,--logo}}[Override logo display with specific vendor]:vendor:(nvidia powerpc arm amd intel apple)' \\"
    );
    println!(
        "        '(-s --style){{-s,--style}}[Set output style]:style:(fancy retro legacy)' \\"
    );
    println!(
        "        '(-c --color){{-c,--color}}[Set color scheme]:scheme:(intel intel-new amd ibm arm rockchip sifive)' \\"
    );
    println!("        '(-r --raw){{-r,--raw}}[Dump raw cpuid data]' \\");
    println!(
        "        '(-F --full-cpu-name){{-F,--full-cpu-name}}[Show the unabbreviated CPU name]' \\"
    );
    println!("        '--logo-long[Force the large logo variant]' \\");
    println!("        '--logo-short[Force the small logo variant]' \\");
    println!("        '--logo-intel-new[Force the modern Intel logo]' \\");
    println!("        '--logo-intel-old[Force the classic Intel logo]' \\");
    println!("        '--accurate-pp[Measure peak performance instead of estimating it]' \\");
    println!(
        "        '--measure-max-freq[Measure max frequency instead of reading it from the OS]' \\"
    );
    println!(
        "        '(-d --debug){{-d,--debug}}[Print low-level identifying register info and exit]' \\"
    );
    println!(
        "        '(-v --verbose){{-v,--verbose}}[Print diagnostic warnings about info gathering]' \\"
    );
    println!("        '--completions[Generate shell completions]:shell:(fish bash zsh)'");
    println!("}}");
    println!();
    println!("_rcpufetch \"$@\"");
}