summaryrefslogblamecommitdiffstats
path: root/build.rs
blob: f67402f8c06bf89d3a5e5df100809eefd1421831 (plain) (tree)


















































































































































                                                                                                                    
#![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");
}