// build.rs — auto-generates src/modules/registry.rs // // Scans src/modules/ for subdirectories containing a mod.rs file. // Each subdirectory name like "home" maps to a struct "HomeModule" // defined in src/modules/home/mod.rs. // // Rules for a directory to be picked up: // • Located directly in src/modules/ // • Contains a mod.rs file // • Does NOT contain plugin.toml (add-ons register via BuiltinPlugin::ribbon) // • Defines a pub struct {PascalCase}Module implementing CadModule // (unit struct — no ::new() needed) // // Tab display order: the PRIORITY list below controls order. // Unlisted modules appear after priority modules, sorted alphabetically. // // Adding a new module: // 1. Create src/modules/my_name/mod.rs (implement CadModule as MyNameModule) // 2. Add `pub mod my_name;` to src/modules/mod.rs // 3. cargo build → module appears in the ribbon use std::fs; use std::path::Path; // Display order for known modules; unknown modules follow alphabetically. const PRIORITY: &[&str] = &["home", "model", "insert", "annotate", "view", "manage"]; fn main() { let mods_dir = Path::new("src/modules"); println!("cargo:rerun-if-changed=src/modules"); // Collect subdirectories that contain mod.rs. let mut modules: Vec = fs::read_dir(mods_dir) .expect("src/modules/ not found") .filter_map(|e| { let entry = e.ok()?; if !entry.file_type().ok()?.is_dir() { return None; } let name = entry.file_name().into_string().ok()?; let mod_file = mods_dir.join(&name).join("mod.rs"); let plugin_toml = mods_dir.join(&name).join("plugin.toml"); if mod_file.exists() && !plugin_toml.exists() { Some(name) } else { None } }) .collect(); // Sort: priority order first, then alphabetical for the rest. modules.sort_by_key(|name| { let pri = PRIORITY.iter().position(|p| *p == name.as_str()); (pri.map(|i| i as u32).unwrap_or(u32::MAX), name.clone()) }); // snake_case → PascalCase + "Module" let to_struct = |s: &str| -> String { s.split('_') .map(|w| { let mut c = w.chars(); match c.next() { None => String::new(), Some(f) => f.to_uppercase().collect::() + c.as_str(), } }) .collect::() + "Module" }; let mut out = String::from( "// AUTO-GENERATED by build.rs — do not edit by hand.\n\ // To add a module: create src/modules/my_name/mod.rs + add `pub mod my_name;` to modules/mod.rs.\n\n\ use crate::modules::CadModule;\n\n\ /// Returns one boxed instance of every registered CAD module.\n\ /// Called once at startup by `Ribbon::new()`.\n\ pub fn all_modules() -> Vec> {\n\ \tvec![\n", ); for module in &modules { let s = to_struct(module); out.push_str(&format!("\t\tBox::new(super::{module}::{s}),\n")); } out.push_str("\t]\n}\n"); let out_path = mods_dir.join("registry.rs"); let current = fs::read_to_string(&out_path).unwrap_or_default(); if current != out { fs::write(&out_path, &out).expect("failed to write registry.rs"); } }