#![feature(iter_intersperse)] use std::env; use std::fs; use std::panic; use std::io::Write; use std::path::Path; fn main() { let src_dir = Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("src"); let tpl_loc = src_dir.join("template.rs"); let tpl_contents = fs::read_to_string(&tpl_loc).unwrap(); let mut tpl_indent = None; let (tpl_head, tpl_tail): (Vec<_>, Vec<_>) = { let mut tpl_filter = false; let mut tpl_end = false; tpl_contents.lines() .filter(|line| if !tpl_filter { if line.trim().starts_with("// CODEGEN START") { tpl_indent = Some(line.len() - line.trim_start().len()); tpl_filter = true; } true } else { if line.trim().starts_with("// CODEGEN END") { tpl_filter = false; true } else { false } }) .partition(|line| if !tpl_end { if line.trim().starts_with("// CODEGEN START") { tpl_end = true; } true } else { false }) }; let tpl_indent = " ".repeat(tpl_indent.unwrap()); let default_hook = panic::take_hook(); let panic_body = tpl_head.iter().chain(&tpl_tail).map(|a| *a).intersperse("\n").collect::() + "\n"; let tpl_loc2 = tpl_loc.clone(); panic::set_hook(Box::new(move |pi| { { let mut tpl_fp = fs::File::create(&tpl_loc2).unwrap(); write!(tpl_fp, "{panic_body}").unwrap(); } default_hook(pi); })); let mut tpl_fp = fs::File::create(&tpl_loc).unwrap(); writeln!(tpl_fp, "{}", tpl_head.join("\n")).unwrap(); let tpl_dir = src_dir.join("template"); for (n, ent) in tpl_dir.read_dir().unwrap().enumerate() { let ent = ent.unwrap(); if !ent.file_type().unwrap().is_file() { continue; } if !ent.file_name().as_encoded_bytes().ends_with(b".rs") { continue; } let modname = format!("mod{n}"); let path = ent.path(); writeln!(tpl_fp, "{tpl_indent}#[path = \"{}\"] mod {modname};", path.display()).unwrap(); let file = syn::parse_file(&fs::read_to_string(ent.path()).unwrap()).unwrap(); for item in &file.items { let syn::Item::Fn(func) = item else { continue; }; let syn::Visibility::Public(_) = func.vis else { continue; }; let name = func.sig.ident.to_string(); println!("{}: {}", path.display(), func.sig.ident); if name.starts_with("win_") { assert_eq!(func.sig.inputs.len(), 1, "Window function may only take one arg!"); let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] else { panic!("Window function must take `_: &[mut] egui::Ui`, not `self`!"); }; let syn::Type::Reference(syn::TypeReference{ elem: ref ty, .. }) = **ty else { panic!("Window function must take `_: &[mut] egui::Ui`!"); }; let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty else { panic!("Window function must take `_: &[mut] egui::Ui`!"); }; assert_eq!(segments.last().unwrap().ident, "Ui", "Window function must take `_: &[mut] egui::Ui`!"); let name_tok = file.items.iter().find_map(|i| { if let syn::Item::Const(c) = i { let syn::Visibility::Public(_) = c.vis else { return None; }; if c.ident == name.to_uppercase() { return Some(c.ident.to_string()); } } if let syn::Item::Static(s) = i { let syn::Visibility::Public(_) = s.vis else { return None; }; if s.ident == name.to_uppercase() { return Some(s.ident.to_string()); } } None }).map(|a| modname.clone() + "::" + &a).unwrap_or_else(|| format!("\"{name}\"")); writeln!(tpl_fp, "{tpl_indent}egui::Window::new({name_tok}).show(ctx, {modname}::{name});").unwrap(); } else if name.starts_with("ctx_") { assert_eq!(func.sig.inputs.len(), 1, "Context function may only take one arg!"); let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] else { panic!("Context function must take `_: &egui::Context`, not `self`!"); }; let syn::Type::Reference(syn::TypeReference{ mutability, elem: ref ty, .. }) = **ty else { panic!("Context function must take `_: &egui::Context`!"); }; let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty else { panic!("Context function must take `_: &egui::Context`!"); }; assert_eq!(segments.last().unwrap().ident, "Context", "Window function must take `_: &egui::Context`!"); assert!(mutability.is_none(), "Context function must take immutable reference!"); writeln!(tpl_fp, "{tpl_indent}{modname}::{name}(ctx);").unwrap(); } else if name.starts_with("paint_bg_") { assert_eq!(func.sig.inputs.len(), 1, "Painter function may only take one arg!"); let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] else { panic!("Painter function must take `_: egui::Painter`, not `self`!"); }; let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty else { panic!("Painter function must take `_: egui::Painter`!"); }; assert_eq!(segments.last().unwrap().ident, "Painter", "Painter function must take `_: egui::Painter`!"); writeln!(tpl_fp, "{tpl_indent}{modname}::{name}(ctx.layer_painter(egui::LayerId::background()));").unwrap(); } else if name.starts_with("paint_dbg_") { assert_eq!(func.sig.inputs.len(), 1, "Painter function may only take one arg!"); let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] else { panic!("Painter function must take `_: egui::Painter`, not `self`!"); }; let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty else { panic!("Painter function must take `_: egui::Painter`!"); }; assert_eq!(segments.last().unwrap().ident, "Painter", "Painter function must take `_: egui::Painter`!"); writeln!(tpl_fp, "{tpl_indent}{modname}::{name}(ctx.layer_painter(egui::LayerId::debug()));").unwrap(); } } } writeln!(tpl_fp, "{}", tpl_tail.join("\n")).unwrap(); println!("cargo::rerun-if-changed=src/template/"); println!("cargo::rerun-if-changed=src/template.rs"); }