fixed-json 0.2.1

No-std, no-allocation JSON parsing into caller-owned fixed storage
Documentation
use std::fs;
use std::io::{self, Write};
use std::path::Path;

fn main() -> io::Result<()> {
    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set");
    let suite_dir = Path::new(&manifest_dir).join("JSONTestSuite/test_parsing");
    let out_path = Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR is set"))
        .join("json_test_suite_cases.rs");

    println!("cargo:rerun-if-changed={}", suite_dir.display());

    let mut files = Vec::new();
    if suite_dir.exists() {
        for entry in fs::read_dir(&suite_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().is_some_and(|ext| ext == "json") {
                files.push(entry.file_name().to_string_lossy().into_owned());
            }
        }
    }
    files.sort();

    let mut out = fs::File::create(out_path)?;
    for (index, file) in files.into_iter().enumerate() {
        println!("cargo:rerun-if-changed={}", suite_dir.join(&file).display());
        let test_name = test_name(index, &file);
        let expect = match file.as_bytes().first().copied() {
            Some(b'y') => "Accept",
            Some(b'n') => "Reject",
            Some(b'i') => "ImplementationDefined",
            _ => "ImplementationDefined",
        };
        writeln!(
            out,
            "#[test]\nfn {test_name}() {{ run_case({file:?}, include_bytes!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/JSONTestSuite/test_parsing/{file}\")), Expect::{expect}); }}\n"
        )?;
    }

    Ok(())
}

fn test_name(index: usize, file: &str) -> String {
    let stem = file.strip_suffix(".json").unwrap_or(file);
    let mut name = format!("json_suite_{index:03}_");
    let mut last_was_underscore = false;
    for ch in stem.chars() {
        if ch.is_ascii_alphanumeric() {
            name.push(ch.to_ascii_lowercase());
            last_was_underscore = false;
        } else if !last_was_underscore {
            name.push('_');
            last_was_underscore = true;
        }
    }
    while name.ends_with('_') {
        name.pop();
    }
    if name.ends_with(|ch: char| ch.is_ascii_digit()) {
        name.push_str("_case");
    }
    if name.is_empty() {
        format!("json_suite_{index:03}_case")
    } else {
        name
    }
}