use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
fn main() {
let blocks_dir = Path::new("src/blocks");
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_path = PathBuf::from(&out_dir).join("block_registry.rs");
println!("cargo::rerun-if-changed=src/blocks");
let mut blocks: BTreeMap<String, Vec<(String, String)>> = BTreeMap::new();
let entries = fs::read_dir(blocks_dir).expect("Failed to read src/blocks/");
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
let category = path.file_name().unwrap().to_string_lossy().into_owned();
if matches!(category.as_str(), "utils") {
continue;
}
let mut category_blocks = Vec::new();
let files = fs::read_dir(&path).expect("Failed to read block category dir");
for file in files.flatten() {
let file_path = file.path();
if file_path.extension().is_none_or(|ext| ext != "rs") {
continue;
}
let file_name = file_path
.file_stem()
.unwrap()
.to_string_lossy()
.into_owned();
if let Some(struct_name) = find_block_struct(&file_path) {
category_blocks.push((file_name, struct_name));
}
}
category_blocks.sort_by(|a, b| a.1.cmp(&b.1));
if !category_blocks.is_empty() {
blocks.insert(category, category_blocks);
}
}
let mut code = String::new();
for (category, category_blocks) in &blocks {
for (module, struct_name) in category_blocks {
code.push_str(&format!(
"use crate::blocks::{category}::{module}::{struct_name};\n"
));
}
}
code.push('\n');
code.push_str("register_blocks!(\n");
let total_categories = blocks.len();
for (cat_idx, (category, category_blocks)) in blocks.iter().enumerate() {
code.push_str(&format!(" // {} blocks\n", capitalize(category)));
for (i, (_, struct_name)) in category_blocks.iter().enumerate() {
code.push_str(&format!(" {struct_name}"));
if cat_idx < total_categories - 1 || i < category_blocks.len() - 1 {
code.push(',');
}
code.push('\n');
}
}
code.push_str(");\n");
fs::write(&out_path, code).expect("Failed to write block registry");
}
fn find_block_struct(path: &Path) -> Option<String> {
let content = fs::read_to_string(path).ok()?;
let mut found_block_attr = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed == "#[block]" {
found_block_attr = true;
continue;
}
if found_block_attr {
if trimmed.starts_with("#[") || trimmed.starts_with("//") {
continue;
}
if let Some(rest) = trimmed.strip_prefix("pub struct ") {
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() {
return Some(name);
}
}
found_block_attr = false;
}
}
None
}
fn capitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
}
}