FerroBabe 0.1.3

A lightweight Rust library for disassembling Java class files.
Documentation
use std::{
    env,
    fs::File,
    path::{Path, PathBuf},
    process::ExitCode,
};

use ferro_babe::{Diagnostic, Disassembler, FerroFormatter, Formatter};

fn main() -> ExitCode {
    run().unwrap_or_else(|message| {
        eprintln!("ferro-disassemble: {message}");
        ExitCode::FAILURE
    })
}

fn run() -> Result<ExitCode, String> {
    let path = class_path()?;
    let file =
        File::open(&path).map_err(|error| format!("could not open {}: {error}", path.display()))?;
    let disassembly = Disassembler::default()
        .parse_reader(file)
        .map_err(|error| format!("could not parse {}: {error}", path.display()))?;

    let version = disassembly.version();
    let Some(class) = disassembly.class() else {
        println!(
            "partial class file v{}.{}",
            version.major(),
            version.minor()
        );
        for diagnostic in disassembly.diagnostics() {
            print_diagnostic(diagnostic);
        }
        return Ok(ExitCode::from(2));
    };

    let output = FerroFormatter
        .format(class)
        .map_err(|error| format!("could not format {}: {error}", path.display()))?;
    print!("{output}");

    Ok(ExitCode::SUCCESS)
}

fn class_path() -> Result<PathBuf, String> {
    let mut arguments = env::args_os();
    let executable = arguments.next().unwrap_or_default();
    let Some(path) = arguments.next() else {
        return Err(usage(&executable));
    };

    if arguments.next().is_some() {
        return Err(usage(&executable));
    }

    Ok(PathBuf::from(path))
}

fn usage(executable: &std::ffi::OsStr) -> String {
    format!("usage: {} <class-file>", Path::new(executable).display())
}

fn print_diagnostic(diagnostic: &Diagnostic) {
    let offset = diagnostic
        .offset()
        .map(|offset| format!(" at code offset 0x{offset:04x}"))
        .unwrap_or_default();

    eprintln!(
        "diagnostic: {:?} in {:?}{offset}: {}",
        diagnostic.severity(),
        diagnostic.stage(),
        diagnostic.message()
    );
}