criew 0.0.1

Terminal-first Linux kernel patch mail workflow TUI
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

fn main() {
    println!("cargo:rerun-if-changed=vendor/b4");

    if !generate_embedded_assets() {
        println!("cargo:warning=vendor/b4 not found, embedded b4 fallback disabled");
    }
}

fn generate_embedded_assets() -> bool {
    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR must be available"));
    let manifest_path = out_dir.join("b4_vendor_assets.rs");
    let vendor_root = Path::new("vendor/b4");
    if !vendor_root.exists() {
        write_empty_asset_manifest(&manifest_path).expect("failed to write empty b4 asset list");
        return false;
    }

    let mut files = Vec::new();
    for path in [
        PathBuf::from("vendor/b4/b4.sh"),
        PathBuf::from("vendor/b4/COPYING"),
        PathBuf::from("vendor/b4/patatt/COPYING"),
    ] {
        if path.exists() {
            files.push(path);
        }
    }

    collect_runtime_assets(Path::new("vendor/b4/src/b4"), &mut files)
        .expect("failed to scan vendor/b4/src/b4");
    collect_runtime_assets(Path::new("vendor/b4/patatt/patatt"), &mut files)
        .expect("failed to scan vendor/b4/patatt/patatt");
    files.sort();

    let mut manifest =
        String::from("// Auto-generated by build.rs.\nconst ASSETS: &[Asset] = &[\n");
    for path in files {
        let relative = path
            .strip_prefix(vendor_root)
            .expect("asset path must stay under vendor/b4");
        let relative = relative.to_string_lossy().replace('\\', "/");
        let absolute = path
            .canonicalize()
            .expect("failed to canonicalize embedded b4 asset path");
        let executable = relative == "b4.sh";
        manifest.push_str(&format!(
            "    Asset {{ relative_path: {:?}, contents: include_bytes!({:?}), executable: {} }},\n",
            relative,
            absolute.display().to_string(),
            executable
        ));
    }
    manifest.push_str("];\n");
    fs::write(&manifest_path, manifest).expect("failed to write b4 asset manifest");
    true
}

fn write_empty_asset_manifest(path: &Path) -> std::io::Result<()> {
    fs::write(path, "const ASSETS: &[Asset] = &[];\n")
}

fn collect_runtime_assets(root: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
    if !root.exists() {
        return Ok(());
    }

    for entry in fs::read_dir(root)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() {
            if path.file_name().is_some_and(|name| name == "__pycache__") {
                continue;
            }
            collect_runtime_assets(&path, out)?;
            continue;
        }

        out.push(path);
    }

    Ok(())
}