crabsoup 0.1.0-alpha1

A Rust re-implementation of soupault.
Documentation
use anyhow::Result;
use crabsoup_mlua_analyze::LuaAnalyzerBuilder;
use mlua::Compiler;
use std::path::PathBuf;

fn compile_script(input: &[u8], has_require: bool) -> Vec<u8> {
    let mut compiler = Compiler::new()
        .set_optimization_level(2)
        .set_type_info_level(1);
    if has_require {
        compiler =
            compiler.set_mutable_globals(vec!["require".to_string(), "require_env".to_string()]);
    }
    compiler.compile(input)
}

pub fn main() -> Result<()> {
    let mut out_path = PathBuf::from(std::env::var("OUT_DIR")?);
    out_path.push("luau_compiled");
    std::fs::create_dir_all(&out_path)?;

    let mut all_paths = Vec::new();

    let mut analyze = LuaAnalyzerBuilder::new();
    analyze.add_definitions("defs_shared.d_lua", include_str!("lua/defs/defs_shared.d_lua"));
    analyze
        .add_definitions("defs_standalone.d_lua", include_str!("lua/defs/defs_standalone.d_lua"));
    let analyze = analyze.build();
    for path in glob::glob("lua/**/*")? {
        let path = path?;
        let file_name = path.file_name().unwrap().to_string_lossy();
        let suffix = path.strip_prefix("lua/")?;
        let str_path = path
            .to_string_lossy()
            .strip_prefix("lua/")
            .unwrap()
            .to_string();
        if path.is_file() && (file_name.ends_with(".lua") || file_name.ends_with(".luau")) {
            let mut output = out_path.clone();
            output.push(suffix);

            let script = std::fs::read_to_string(&path)?;
            let name = path.file_name().unwrap().to_string_lossy();
            let result = analyze.check(&name, &script, false);

            if result.iter().any(|x| x.is_error) {
                println!("{result:#?}");
                panic!("Error in script: {name}");
            }

            let mut parent = output.clone();
            parent.pop();
            std::fs::create_dir_all(parent)?;
            std::fs::write(
                &output,
                compile_script(script.as_bytes(), !suffix.starts_with("app/")),
            )?;

            all_paths.push((str_path, output.to_string_lossy().to_string()));
        } else if path.is_file() {
            all_paths.push((str_path, path.canonicalize()?.to_string_lossy().to_string()));
        }
        println!("cargo::rerun-if-changed={}", path.display());
    }

    out_path.pop();
    out_path.push("luau_modules.rs");

    let mut push_lines = Vec::new();
    for (k, v) in all_paths {
        push_lines.push(format!(r#"map.insert({k:?}, include_bytes!({v:?}).as_slice());"#));
    }
    let push_lines = push_lines.join("\n");
    std::fs::write(
        out_path,
        format!(
            r#"
                fn load_lua_sources() -> crate::wyhash::WyHashMap<&'static str, &'static [u8]> {{
                    let mut map = crate::wyhash::WyHashMap::default();
                    {push_lines}
                    map
                }}
            "#
        ),
    )?;

    Ok(())
}