summaryrefslogtreecommitdiffstats
path: root/build.rs
blob: f67402f8c06bf89d3a5e5df100809eefd1421831 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#![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");
}