#![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::<String>() + "\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");
}