Skip to main content

rusteal_codegen/rust_gen/
mod.rs

1// Rust code generation orchestrator.
2
3pub mod enums;
4pub mod structs;
5pub mod classes;
6pub mod properties;
7pub mod delegates;
8pub mod module;
9pub mod func_ids;
10pub mod param_helpers;
11pub mod cargo_toml;
12pub mod manual;
13pub mod prelude;
14
15use std::collections::HashSet;
16use std::path::Path;
17
18use crate::context::CodegenContext;
19use crate::write_if_changed;
20
21/// Generate all Rust code into the output directory.
22pub fn generate(ctx: &CodegenContext, out_dir: &Path) {
23    // Ensure output directory exists
24    std::fs::create_dir_all(out_dir).expect("Failed to create Rust output directory");
25    remove_disabled_modules(ctx, out_dir);
26
27    // Generate per-module code
28    for module_name in ctx.enabled_modules.iter() {
29        let module_dir = out_dir.join(module_name);
30        std::fs::create_dir_all(&module_dir).expect("Failed to create module directory");
31        // The files this run writes: the others are types dropped since
32        // (blocklist, engine upgrade), removed at the end.
33        let mut written: HashSet<String> = HashSet::new();
34
35        // Enums
36        if let Some(module_enums) = ctx.module_enums.get(module_name) {
37            for e in module_enums {
38                let code = enums::generate_enum(e);
39                let filename = crate::naming::to_snake_case(&e.name) + ".rs";
40                write_if_changed(&module_dir.join(&filename), &code)
41                    .unwrap_or_else(|err| panic!("Failed to write {filename}: {err}"));
42                written.insert(filename);
43            }
44        }
45
46        // Structs
47        if let Some(module_structs) = ctx.module_structs.get(module_name) {
48            for s in module_structs {
49                let code = structs::generate_struct(s, ctx);
50                let filename = crate::naming::to_snake_case(&s.name) + ".rs";
51                write_if_changed(&module_dir.join(&filename), &code)
52                    .unwrap_or_else(|err| panic!("Failed to write {filename}: {err}"));
53                written.insert(filename);
54            }
55        }
56
57        // Classes
58        if let Some(module_classes) = ctx.module_classes.get(module_name) {
59            for c in module_classes {
60                let code = classes::generate_class(c, ctx);
61                let filename = crate::naming::to_snake_case(&c.name) + ".rs";
62                write_if_changed(&module_dir.join(&filename), &code)
63                    .unwrap_or_else(|err| panic!("Failed to write {filename}: {err}"));
64                written.insert(filename);
65            }
66        }
67
68        // Module mod.rs
69        let mod_code = module::generate_module_mod(
70            module_name,
71            ctx.module_enums.get(module_name).map(|v| v.as_slice()),
72            ctx.module_structs.get(module_name).map(|v| v.as_slice()),
73            ctx.module_classes.get(module_name).map(|v| v.as_slice()),
74        );
75        write_if_changed(&module_dir.join("mod.rs"), &mod_code)
76            .expect("Failed to write module mod.rs");
77        written.insert("mod.rs".to_string());
78        remove_unwritten(&module_dir, &written);
79    }
80
81    // Generate func_ids.rs
82    let func_ids_code = func_ids::generate_rust_func_ids(&ctx.func_table);
83    write_if_changed(&out_dir.join("func_ids.rs"), &func_ids_code)
84        .expect("Failed to write func_ids.rs");
85
86    // Generate top-level lib.rs
87    let lib_code = module::generate_lib_rs(ctx);
88    write_if_changed(&out_dir.join("lib.rs"), &lib_code).expect("Failed to write lib.rs");
89}
90
91/// Remove the entries of a module directory this run did not write.
92fn remove_unwritten(module_dir: &Path, written: &HashSet<String>) {
93    let Ok(entries) = std::fs::read_dir(module_dir) else {
94        return;
95    };
96    for entry in entries.flatten() {
97        let name = entry.file_name().to_string_lossy().into_owned();
98        if written.contains(&name) {
99            continue;
100        }
101        let path = entry.path();
102        let removed = if path.is_dir() {
103            std::fs::remove_dir_all(&path)
104        } else {
105            std::fs::remove_file(&path)
106        };
107        removed.unwrap_or_else(|e| panic!("Failed to remove stale {}: {e}", path.display()));
108    }
109}
110
111/// Delete the module directories of modules this run does not generate. `lib.rs`
112/// stops declaring a module when its feature is turned off, but its directory
113/// would stay behind. Only directories whose `mod.rs` carries the codegen header
114/// are removed; `manual/` is rewritten by `manual::write_manual_module`.
115fn remove_disabled_modules(ctx: &CodegenContext, out_dir: &Path) {
116    let Ok(entries) = std::fs::read_dir(out_dir) else {
117        return;
118    };
119    for entry in entries.flatten() {
120        let path = entry.path();
121        let name = entry.file_name().to_string_lossy().into_owned();
122        if !path.is_dir() || name == "manual" || ctx.enabled_modules.contains(&name) {
123            continue;
124        }
125        let generated = std::fs::read_to_string(path.join("mod.rs"))
126            .is_ok_and(|m| m.starts_with("// Auto-generated by rusteal-codegen."));
127        if generated {
128            std::fs::remove_dir_all(&path)
129                .unwrap_or_else(|e| panic!("Failed to remove {}: {e}", path.display()));
130            eprintln!("  removed {name}/ (module no longer enabled)");
131        }
132    }
133}