dc-runner-cli 0.2.7

Required Rust runner lane for Data Contracts.
use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};

fn collect_files(root: &Path, out: &mut Vec<PathBuf>) {
    let Ok(entries) = fs::read_dir(root) else {
        return;
    };
    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_dir() {
            collect_files(&path, out);
        } else if path.is_file() {
            out.push(path);
        }
    }
}

fn to_unix(path: &Path) -> String {
    path.to_string_lossy().replace('\\', "/")
}

fn emit_snapshot(source_root: &Path, prefix: &str, out_name: &str) {
    println!("cargo:rerun-if-changed={}", source_root.display());

    let mut files = Vec::<PathBuf>::new();
    collect_files(&source_root, &mut files);
    files.sort();

    let mut entries = Vec::<(String, String)>::new();
    let mut hasher = Sha256::new();
    for file in files {
        let rel = file
            .strip_prefix(&source_root)
            .expect("strip prefix for source root");
        let key = format!("{}/{}", prefix.trim_end_matches('/'), to_unix(rel));
        let text = fs::read_to_string(&file)
            .unwrap_or_else(|e| panic!("failed to read {}: {e}", file.display()));
        hasher.update(key.as_bytes());
        hasher.update(b"\n");
        hasher.update(text.as_bytes());
        hasher.update(b"\n");
        entries.push((key, text));
    }

    let snapshot_sha256 = format!("{:x}", hasher.finalize());
    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
    let out_path = out_dir.join(out_name);
    let mut out = fs::File::create(&out_path)
        .unwrap_or_else(|e| panic!("failed to create {}: {e}", out_path.display()));

    writeln!(
        out,
        "pub const SNAPSHOT_SOURCE_ROOT: &str = {:?};",
        to_unix(&source_root)
    )
    .expect("write source root");
    writeln!(
        out,
        "pub const SNAPSHOT_SHA256: &str = {:?};",
        snapshot_sha256
    )
    .expect("write sha");
    writeln!(out, "pub static FILES: &[(&str, &str)] = &[").expect("write header");
    for (key, text) in entries {
        writeln!(out, "    ({:?}, {:?}),", key, text).expect("write row");
    }
    writeln!(out, "];").expect("write footer");
}

fn main() {
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
    let core_candidates = [
        manifest_dir.join("specs/upstream/data-contracts"),
        manifest_dir.join("../specs/upstream/data-contracts"),
    ];
    let core_root = core_candidates
        .iter()
        .find(|p| p.exists() && p.is_dir())
        .cloned()
        .expect("missing data-contracts snapshot source for embedding");
    emit_snapshot(
        &core_root,
        "specs/upstream/data-contracts",
        "embedded_data_contracts.rs",
    );

    if env::var("CARGO_FEATURE_BUNDLER").is_ok() {
        let bundler_candidates = [
            manifest_dir.join("specs/upstream/data-contracts-library"),
            manifest_dir.join("../specs/upstream/data-contracts-library"),
        ];
        let bundler_root = bundler_candidates
            .iter()
            .find(|p| p.exists() && p.is_dir())
            .cloned()
            .expect("missing data-contracts-library snapshot source for bundler embedding");
        emit_snapshot(
            &bundler_root,
            "specs/upstream/data-contracts-library",
            "embedded_data_contracts_library.rs",
        );
    }
}