lihaaf 0.1.0-beta.1

A CLI proc-macro test harness for Rust that builds a crate into a dylib once, then attempts compiling fixtures against it with per-fixture rustc dispatch (a la trybuild) — adding more fixtures stays cheap.
Documentation
//! `cargo-lihaaf` — Cargo subcommand entry point.
//!
//! Cargo discovers binaries on `PATH` named `cargo-<subcommand>` and
//! invokes them with the subcommand string as the first positional
//! argument. That argument is stripped, then the remainder is handed to
//! [`lihaaf::cli`] for parsing.
//!
//! ## Why a binary, not a library entry point
//!
//! This crate ships as a Cargo subcommand binary because users interact through
//! CLI anyway; keeping that as the primary entry keeps compatibility and semver
//! expectations straightforward.
//!
//! ## Exit codes
//!
//! Exit-code mapping lives in [`lihaaf::exit::ExitCode`]. This binary turns an
//! [`lihaaf::Error`] into the matching session exit code.
//! Per-fixture verdict aggregation runs inside [`lihaaf::run`]
//! and is returned as part of the success path’s report.
//! [`lihaaf::Report`].

use std::process::ExitCode as ProcessExitCode;

use lihaaf::cli;
use lihaaf::{Error, ExitCode, run};

fn main() -> ProcessExitCode {
    // Cargo passes `lihaaf` as the first positional. Strip it if present.
    // Direct invocation (`cargo-lihaaf --help`) without the prefix also
    // works — the heuristic is "if argv[1] equals `lihaaf` literally,
    // strip it; otherwise leave the argv alone."
    let mut argv: Vec<String> = std::env::args().collect();
    if argv.len() >= 2 && argv[1] == "lihaaf" {
        argv.remove(1);
    }

    let parsed = match cli::parse_from(argv) {
        Ok(p) => p,
        Err(e) => {
            // clap prints its own diagnostic on `--help` / `--version`
            // and on parse errors; the exit code is propagated directly.
            return ProcessExitCode::from(e.exit_code() as u8);
        }
    };

    match run(parsed) {
        Ok(report) => ProcessExitCode::from(report.exit_code() as u8),
        Err(Error::Session(outcome)) => {
            // Pre-v0.1.0-alpha.3 this branch was silent — the exit code
            // told the story but the diagnostic body was dropped on the
            // floor. The multi-suite work expanded the surface area of
            // session-level config errors (`--suite NAME` mismatch,
            // duplicate suite names, fixture_dirs collisions across
            // suites), so the diagnostic message is now printed before
            // the exit-code mapping. The Display impl on Outcome
            // already produces a fully-rendered, byte-deterministic
            // message — see `src/error.rs`.
            eprintln!("{outcome}");
            ProcessExitCode::from(outcome.exit_code() as u8)
        }
        Err(other) => {
            eprintln!("lihaaf: {other}");
            ProcessExitCode::from(ExitCode::ConfigInvalid as u8)
        }
    }
}