rcpufetch 0.0.5

[ALPHA] A rusty crossplatform, but simple CLI binutil for reading CPU information.
# Contributing to rcpufetch

Thank you for your interest in contributing to **rcpufetch**! This document explains
what the project is, how it's built, and how to add to it.

## Table of contents
- [Project overview]#project-overview
- [Relationship to cpufetch]#relationship-to-cpufetch
- [Codebase structure]#codebase-structure
- [Architecture]#architecture
  - [The `cpu` module: arch detection]#the-cpu-module-arch-detection
  - [How the uarch tables were generated]#how-the-uarch-tables-were-generated
  - [OS gatherers]#os-gatherers
  - [The `report`/`style` printer]#the-reportstyle-printer
  - [CLI parsing (`cla.rs`)]#cli-parsing-clars
- [Build process]#build-process
- [Testing]#testing
- [CI]#ci
- [How to contribute]#how-to-contribute
  - [Adding a new microarchitecture / vendor]#adding-a-new-microarchitecture--vendor
  - [Adding a new logo]#adding-a-new-logo
  - [Adding a new OS]#adding-a-new-os
  - [Adding a new CLI flag]#adding-a-new-cli-flag
- [Coding style]#coding-style
- [Reporting issues]#reporting-issues
- [Pull request process]#pull-request-process

---

## Project overview

**rcpufetch** is a fast, cross-platform CLI tool that displays detailed CPU information
in a visually appealing way, including vendor ASCII art. It supports Linux, macOS,
Windows, and FreeBSD/OpenBSD, across x86/x86_64, ARM/AArch64, PowerPC, and RISC-V.

The tool reports: CPU model, vendor, microarchitecture, process node, physical/logical
core count, cache sizes, frequency, and ISA feature flags — rendered next to a colorized
vendor logo, or as plain text with `--no-logo`.

## Relationship to cpufetch

rcpufetch is a **Rust rewrite of [cpufetch](https://github.com/Dr-Noob/cpufetch)**, the
C tool by Dr-Noob. It is not a from-scratch design: the CLI flags, the `--style`/
`--color` model, and the microarchitecture-name lookup tables are ported from upstream
so that command-line muscle memory and output familiarity transfer directly. When in
doubt about *what* a flag should do or *what* a field should be named, upstream
cpufetch's behavior is the tiebreaker — check its
[`src/common/args.c`](https://github.com/Dr-Noob/cpufetch/blob/master/src/common/args.c)
and [`src/common/printer.c`](https://github.com/Dr-Noob/cpufetch/blob/master/src/common/printer.c)
if unsure.

Where rcpufetch differs from upstream on purpose:
- **Zero dependencies.** Everything is `std`-only — no `libc`, no WMI/COM crate, no
  CLI-parsing crate. OS APIs that upstream reaches via C library calls or syscalls are
  instead reached the same way `macos.rs` always has: shelling out to a stable CLI
  (`sysctl`, PowerShell's `Get-CimInstance`) via `std::process::Command`. This keeps
  compile times fast and the dependency tree at exactly one crate.
- **A unified data model.** Upstream C has four almost-identical `print_cpufetch_{x86,arm,ppc,riscv}`
  implementations. rcpufetch has one: every OS gatherer converts its raw data into a
  single `report::CpuReport`, and one printer renders all of them (see
  [Architecture]#architecture).
- **FreeBSD and OpenBSD support**, which upstream doesn't have (upstream builds x86-only
  on FreeBSD and has no OpenBSD support at all) — see [`src/bsd.rs`]src/bsd.rs.
- A few things are intentionally *not* ported yet — see the README's
  [Implementation notes]README.md#implementation-notes. If you want to tackle one of
  those, it's a great first contribution.

## Codebase structure

```
rcpufetch/
├── Cargo.toml               # zero [dependencies] -- std only
├── src/
│   ├── main.rs               # entry point: parse args, dispatch by OS, print
│   ├── cla.rs                 # CLI argument parsing (Args, Style, help/version/completions)
│   ├── style.rs                # --color/--style resolution -> ANSI escape codes
│   ├── report.rs                # CpuReport data model + the one shared printer
│   ├── cpu/                      # architecture-specific detection (OS-independent logic)
│   │   ├── mod.rs                  # ArchInfo (the arch-detection output type)
│   │   ├── x86.rs                   # cpuid-based detection (works on any OS)
│   │   ├── arm.rs                   # MIDR decoding + SoC-name hints
│   │   ├── ppc.rs                   # PVR decoding
│   │   ├── riscv.rs                 # device-tree/isa string matching
│   │   └── tables/                   # generated uarch lookup tables (see below)
│   ├── linux/linux.rs             # Linux gatherer: /proc/cpuinfo, sysfs
│   ├── macos/macos.rs             # macOS gatherer: sysctl
│   ├── windows/windows.rs         # Windows gatherer: PowerShell/WMI
│   ├── bsd.rs                     # FreeBSD/OpenBSD gatherer: sysctl
│   └── art/logos.rs               # vendor ASCII art + color-placeholder substitution
├── scripts/test-in-podman.sh   # local container smoke test (see Testing)
└── .github/workflows/ci.yml   # real multi-platform CI (see CI)
```

### Key files explained

- **`src/main.rs`** — parses CLI args, dispatches on `std::env::consts::OS`
  (`linux`/`macos`/`windows`/`freebsd`/`openbsd`), calls the matching gatherer's
  `new()` then `to_report()`, and hands the result to `report::print`.
- **`src/cla.rs`** — hand-rolled arg parser (no `clap`/`argh`/etc.), the `Style` enum,
  help/version/license text, and shell completion generators.
- **`src/style.rs`** — resolves `--style`/`--color`/`NO_COLOR` into a `Colors` struct
  (3 logo colors + 2 text colors, matching upstream's palette shape) and the effective
  `Style`.
- **`src/report.rs`** — the `CpuReport` struct (the one data shape every OS/arch feeds
  into) and the single `print()` function that lays it out next to the logo.
- **`src/cpu/`** — everything about *identifying* a CPU that doesn't depend on which OS
  you're running on. `x86.rs` genuinely doesn't depend on the OS at all (`cpuid` is a
  plain instruction); `arm.rs`/`ppc.rs`/`riscv.rs` take an already-extracted register
  value or string and turn it into a name, leaving the *how do I get that value on this
  OS* part to the OS gatherer.
- **`src/linux/linux.rs`, `src/macos/macos.rs`, `src/windows/windows.rs`, `src/bsd.rs`**
  — one struct each, with a `new() -> Result<Self, String>` that gathers raw OS data and
  a `to_report() -> CpuReport` that combines it with `crate::cpu` detection.
- **`src/art/logos.rs`** — ASCII art per vendor with `$C1`/`$C2`/`$C3`/`$CR` color
  placeholders, substituted at print time by `report.rs` using the resolved `Colors`.

## Architecture

### The `cpu` module: arch detection

Each architecture module exposes a `detect*` function that turns a raw,
architecture-specific identifier into an `ArchInfo { vendor, uarch, soc, process_nm,
features }`:

| Module | Input | How |
|---|---|---|
| `cpu::x86` | nothing (reads registers itself) | `core::arch::x86_64::__cpuid_count` — vendor string (leaf 0), family/model/stepping (leaf 1), brand string (leaves 0x80000002-4), feature bits (leaves 1 and 7) |
| `cpu::arm` | a raw MIDR `u32` | bit-decodes implementer/part/variant/revision, looks it up in `tables::arm_uarch` |
| `cpu::ppc` | a raw PVR `u32`, or a kernel-decoded name string | masked-PVR lookup in `tables::ppc_uarch`, or substring match on the string |
| `cpu::riscv` | a device-tree `uarch`/`isa` string, or a `marchid` | lookup in `tables::riscv_uarch` |

`cpu::x86` is the only one that's fully self-contained, because `cpuid` is a plain
userspace instruction on every OS — it needs no OS-specific plumbing at all, which is
why `linux.rs`, `macos.rs`, `windows.rs`, and `bsd.rs` all call it identically. ARM/PPC/
RISC-V need the OS layer to hand them a register value or string first (see
[OS gatherers](#os-gatherers)).

### How the uarch tables were generated

`src/cpu/tables/{x86,arm,ppc}_uarch.rs` are **mechanically generated** from upstream
cpufetch's C source (`src/x86/uarch.c`, `src/arm/uarch.c`, `src/ppc/uarch.c`), not
hand-transcribed — those tables are hundreds of entries and hand-copying them would be
both slow and error-prone. If you need to re-sync with upstream after they add new CPUs:

1. Get the current upstream file, e.g. `curl -O https://raw.githubusercontent.com/Dr-Noob/cpufetch/master/src/x86/uarch.c`
2. Write (or adapt) a small regex-based script that extracts each `CHECK_UARCH(...)` /
   `FILL_UARCH(...)` macro invocation's arguments and emits a Rust `static` array —
   this is exactly what was done originally; there's no saved script in-tree since it's
   a one-off per sync, but the table *shape* documents the mapping (see the comment atop
   each generated file).
3. **Preserve wildcard semantics exactly.** Upstream's matching macros treat some fields
   as exact-match-required and others as optional wildcards (`NA` in C, `None` in the
   generated Rust `Option<u32>`). Getting this wrong silently mismatches CPUs. For x86:
   `ext_family` and `family` are always required exact matches; `ext_model`, `model`,
   and `stepping` are wildcards when absent. See the `lookup_uarch` test module in
   `src/cpu/x86.rs` for the exact contract, and add a test case for whatever wildcard
   pattern you're touching.
4. `riscv_uarch.rs` is small enough (single digits of entries) that it's hand-maintained
   directly from upstream's `src/riscv/uarch.c`/`socs.h`.

`arm.rs`'s `SOC_HINTS` table (marketing names like "Snapdragon 8 Gen 3") is **not**
generated — it's a small hand-curated subset of upstream's ~415-entry SoC database. See
the README's [Implementation notes](README.md#implementation-notes) for why, and feel
free to extend it; it's just `(needle, marketing_name)` pairs matched by substring
against `/proc/cpuinfo`'s "Hardware" field or `sysctl`'s `hw.model`.

### OS gatherers

Each OS module follows the same two-method shape:

```rust
pub struct SomeOsCpuInfo {
    model: String,
    vendor: String,
    // ...raw OS-specific fields...
    arch_info: ArchInfo,
}

impl SomeOsCpuInfo {
    pub fn new() -> Result<Self, String> {
        // 1. Gather raw OS data (files/sysctl/WMI)
        // 2. Feed whatever's needed (MIDR bits, PVR, uarch string, or nothing for x86)
        //    into crate::cpu:: to get an ArchInfo
    }

    pub fn to_report(&self) -> CpuReport {
        // Merge raw OS data + arch_info into the one shared shape
    }
}
```

Concretely:
- **Linux** (`linux.rs`): parses `/proc/cpuinfo` (model/vendor/flags/`CPU implementer`/
  `CPU part`/`Hardware`/`cpu`/`uarch`/`isa` fields, whichever apply on the running
  architecture) and sysfs (`/sys/devices/system/cpu/.../cache/`, `.../cpufreq/`) for
  cache and frequency. `detect_arch_info` is `#[cfg(target_arch = ...)]`-gated to call
  the right `crate::cpu` function per architecture.
- **macOS** (`macos.rs`): shells out to `sysctl -n <key>` (`machdep.cpu.*`,
  `hw.cachesize`, `hw.perflevel{0,1}.*` for Apple Silicon P/E-core caches). x86 Macs get
  real `cpuid`-based detection like every other x86 OS; Apple Silicon already gets a
  descriptive name straight from `machdep.cpu.brand_string`.
- **Windows** (`windows.rs`): shells out to PowerShell's `Get-CimInstance Win32_Processor`
  for core counts/cache/frequency (there's no `std`-only WMI API, so this mirrors the
  macOS `sysctl`-shell-out pattern rather than adding a WMI/COM crate), plus
  `std::thread::available_parallelism()` for a reliable logical-core count, plus
  `cpu::x86::detect()` on x86.
- **BSD** (`bsd.rs`, shared by FreeBSD and OpenBSD): shells out to `sysctl -n` for
  `hw.model`/`hw.ncpu`/frequency, same pattern as macOS. One module covers both OSes
  since their relevant sysctl surface is nearly identical — don't split it into
  `freebsd.rs`/`openbsd.rs` unless the two genuinely diverge for a feature you're adding.

### The `report`/`style` printer

`report::CpuReport` is the single shape every gatherer converts into. `report::print()`
is the *only* place that knows how to lay text out next to a logo, wrap it, and color
it — replacing what used to be four separate, nearly-identical `display_info_*`
implementations. If you're adding a new displayed field, it goes in `CpuReport` and in
`info_lines()` inside `report.rs` — not in each OS module.

`style::resolve()` turns `--style`/`--color`/`NO_COLOR` into a `Colors` (3 logo + 2 text
RGB values) and the effective `Style`, matching upstream's palette shape. `art::logos`'s
`get_logo_lines_styled()` takes those resolved colors and substitutes them into the
`$C1`/`$C2`/`$C3` placeholders in the ASCII art (Apple's logo is the one exception —
it keeps its fixed rainbow palette regardless of `--color`, matching how real-world
Apple branding is never recolored to a single accent).

### CLI parsing (`cla.rs`)

Manual parsing over `std::env::args()`, no argument-parsing crate. `Args` holds every
flag; `Args::parse()` is one `match` over `args[i].as_str()`. This mirrors upstream
cpufetch's flag set closely enough that `cpufetch --help` and `rcpufetch --help` should
look like siblings — see [Adding a new CLI flag](#adding-a-new-cli-flag) if you're
porting one upstream has that rcpufetch doesn't yet.

## Build process

```bash
cargo build --release   # zero dependencies, so this is fast and needs no network access
                         # beyond fetching the Rust toolchain itself
./target/release/rcpufetch
```

There is intentionally nothing else in the build: no build script, no codegen step at
build time (the uarch tables are pre-generated source files checked into the repo, not
regenerated on every build), no vendored C code, no linked system libraries beyond what
`std` itself needs. If a change you're making seems to require adding a dependency,
that's worth raising in the PR/issue first — see
[Relationship to cpufetch](#relationship-to-cpufetch) for why that bar is high here.

## Testing

```bash
cargo test        # unit tests: table matching, MIDR decoding, color parsing, formatting
cargo fmt --check
cargo clippy --all-targets -- -D warnings
```

Non-trivial logic (the FMS/MIDR/PVR wildcard-matching lookups, color parsing, cache-size
formatting) has unit tests colocated in the same file under `#[cfg(test)] mod tests`.
If you add a lookup table or matching rule, add a test alongside it in the same style —
particularly for wildcard semantics, which are easy to get subtly wrong (see
[How the uarch tables were generated](#how-the-uarch-tables-were-generated)).

Because this tool fundamentally reports on the machine it runs on, some things can only
really be verified by actually running the binary on real (or real-VM) hardware for that
OS/architecture — that's what CI and `scripts/test-in-podman.sh` are for:

- `scripts/test-in-podman.sh` builds and runs rcpufetch inside a Linux container,
  useful for a quick sanity check on a different kernel than your host (e.g. testing
  the Linux code path from macOS). It only covers your container engine's native
  architecture reliably — cross-arch emulation via qemu-user binfmt was tried and found
  unreliable on at least the `podman machine`/applehv provider; don't rely on it locally.
  Windows and FreeBSD/OpenBSD **cannot** run in a container at all regardless of host —
  containers share the host kernel, and those OSes need their own. That's what CI's real
  VMs are for.

## CI

`.github/workflows/ci.yml` runs on every push/PR:

- **`lint`**: `cargo fmt --check`, `cargo clippy -D warnings`
- **`native`**: real build + `cargo test` + binary smoke tests on `ubuntu-latest`,
  `macos-latest` (arm64), `macos-13` (x86_64), `windows-latest`
- **`cross-linux`**: aarch64/ppc64le/riscv64 built *and executed* under QEMU via
  [`cross`]https://github.com/cross-rs/cross (this is reliable in GitHub's runners,
  unlike local podman-machine emulation — see [Testing]#testing)
- **`freebsd`/`openbsd`**: real VMs via `vmactions/*-vm`, installing Rust natively and
  running the built binary — this is what actually exercises `src/bsd.rs`
- **`cross-check-only`**: compile-only checks for targets with no runnable CI host
  (i686, aarch64-freebsd, x86_64-pc-windows-gnu)

If you're adding a new architecture or OS, add a matching CI job in the same style
rather than only relying on local testing — this project's whole premise is that CPU
detection code is easy to get subtly wrong on hardware/OS combos you don't personally
have, and CI catching it is cheaper than a bug report.

## How to contribute

### Prerequisites
- Rust via [rustup.rs]https://rustup.rs/ (stable toolchain)
- For cross-target checks: `rustup target add <target>`

### Workflow
1. Fork and clone: `git clone https://github.com/yourusername/rcpufetch.git`
2. Branch: `git checkout -b feature/my-new-feature`
3. Build/run: `cargo build && cargo run -- --no-logo`
4. Test: `cargo test && cargo fmt --check && cargo clippy --all-targets -- -D warnings`
5. Commit (see [commit message format]#commit-message-format) and push
6. Open a PR with a clear description, what you tested it on, and screenshots if the
   output changed visually

### Adding a new microarchitecture / vendor

Most of the time this means re-syncing a `tables/*_uarch.rs` file from upstream (see
[How the uarch tables were generated](#how-the-uarch-tables-were-generated)) rather than
writing new detection logic — the FMS/MIDR/PVR decoding logic itself rarely needs to
change, just the table it looks entries up in.

### Adding a new logo

1. Add a `const ASCII_<VENDOR>: &str` to `src/art/logos.rs` using `$C1`/`$C2`/`$C3`
   placeholders for color and `$CR` for reset (keep it under ~50 columns wide so it fits
   next to the text on a normal terminal).
2. Add a match arm in `raw_logo_template()` and in `logo_lines_for_vendor()`'s default
   ANSI-color arrays.
3. If it should have a named `--color` scheme (like `intel`/`amd`/etc.), add a default
   RGB triplet set in `style::Colors::named()`.
4. Add the user-facing name to the `--logo` mapping in `main.rs` and to the help text /
   completions in `cla.rs`.

### Adding a new OS

Follow the shape in [OS gatherers](#os-gatherers): a `new() -> Result<Self, String>`
that gathers raw data plus calls into `crate::cpu`, and a `to_report() -> CpuReport`.
Wire it into the `match os { ... }` in `main.rs`, and add a CI job (a container if the
OS can run in one, a VM action like `vmactions` if not — see [CI](#ci)).

### Adding a new CLI flag

1. Add a field to `Args` in `cla.rs` and a match arm in `Args::parse()`.
2. Add it to `print_help()` and all three shell-completion functions
   (`print_fish_completions`/`print_bash_completions`/`print_zsh_completions`) — yes,
   all three; there's no single source of truth for the flag list yet, which is worth
   fixing if you're adding several flags at once.
3. Wire the behavior into `main.rs`/`report.rs` as appropriate.
4. Update the flag table in `README.md`.
5. If the flag exists in upstream cpufetch, match its name/semantics exactly (see
   [Relationship to cpufetch]#relationship-to-cpufetch).

## Coding style

- `///` doc comments on public items; module-level `//!` docs explaining *why* a module
  exists when that's non-obvious from its name.
- `Result<T, String>` for fallible operations that report to the user; avoid adding a
  custom error-enum/`anyhow`-style dependency for this — string errors are fine here.
- Comments explain *why*, not *what* — see the comments in `bsd.rs`/`x86.rs`/`report.rs`
  explaining scoped-down simplifications (e.g. `arm.rs`'s `SOC_HINTS` docstring) for the
  house style: name the simplification and, where there's a known ceiling, what would
  justify lifting it.
- No dependencies without discussion first (see
  [Relationship to cpufetch]#relationship-to-cpufetch and [Build process]#build-process).
- Run `cargo fmt` and `cargo clippy --all-targets -- -D warnings` before opening a PR —
  CI enforces both.

## Reporting issues

**Bug reports** should include: OS + version, CPU model/vendor, `rcpufetch --version`
output, the full error/incorrect output, what you expected instead, and — if it's a
detection bug — the relevant raw source (`/proc/cpuinfo` on Linux, `sysctl -a | grep
machdep.cpu` on macOS/BSD) so the table/parsing logic can be fixed against real data.

**Feature requests** should say which OS(es)/architecture(s) it applies to and, ideally,
where the data would come from (a sysfs path, a `sysctl` key, a WMI class) — that's
usually the actual hard part.

## Pull request process

- One feature/fix per PR, with a clear description of *why*, not just *what*.
- Update `README.md`/this file if you changed user-facing behavior or architecture.
- CI (lint + native + cross-linux + freebsd + openbsd) must pass.
- Note what you actually tested on (which OS/architecture, real hardware vs. VM vs.
  `scripts/test-in-podman.sh`) — given this project's whole surface area is
  "behaves correctly across many OS/CPU combos," this is more useful than usual.

### Commit message format
Conventional commits: `type(scope): brief description`, types `feat`/`fix`/`docs`/
`style`/`refactor`/`test`/`chore`. Examples:
```
feat(arm): add Snapdragon 8 Gen 4 to SOC_HINTS
fix(linux): correct physical core fallback on ARM (no physical id/core id keys)
docs(contributing): document uarch table generation process
```

---

Thank you for helping make **rcpufetch** better!