forge_codegen/
compiler.rs1use std::fs;
5use std::path::{Path, PathBuf};
6
7use walkdir::WalkDir;
8
9use crate::lower::{lower, lower_with_target, LowerTarget};
10use crate::parser::tokenize;
11
12pub fn compile_source(source: &str) -> String {
13 let tokens = tokenize(source);
14 lower(&tokens)
15}
16
17pub fn compile_source_runtime(source: &str) -> String {
22 let tokens = tokenize(source);
23 lower_with_target(&tokens, LowerTarget::MiniJinja)
24}
25
26pub fn compile_file(input: &Path, output: &Path) -> std::io::Result<()> {
27 let raw = fs::read_to_string(input)?;
28 let lowered = compile_source(&raw);
29 if let Some(parent) = output.parent() {
30 fs::create_dir_all(parent)?;
31 }
32 fs::write(output, lowered)?;
33 Ok(())
34}
35
36pub fn emit_embedded_registry(input_dir: &Path, output_rs: &Path) -> std::io::Result<()> {
52 if let Some(parent) = output_rs.parent() {
53 fs::create_dir_all(parent)?;
54 }
55
56 if !input_dir.exists() {
57 fs::write(
58 output_rs,
59 "// forge-codegen: input dir not found at build time, no templates embedded.\n",
60 )?;
61 return Ok(());
62 }
63
64 let abs_input = fs::canonicalize(input_dir)?;
65 let mut out = String::from(
66 "// Generated by forge_codegen::emit_embedded_registry — do not edit.\n",
67 );
68
69 for entry in WalkDir::new(&abs_input).into_iter().filter_map(|e| e.ok()) {
70 if !entry.file_type().is_file() {
71 continue;
72 }
73 let path = entry.path();
74 let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
75 continue;
76 };
77 if !file_name.ends_with(".forge.html") && !file_name.ends_with(".forge") {
78 continue;
79 }
80 let rel = path.strip_prefix(&abs_input).unwrap_or(path);
81 let rel_str = rel.to_string_lossy().replace('\\', "/");
82 let view_path = rel_str
83 .trim_end_matches(".forge.html")
84 .trim_end_matches(".forge")
85 .to_string();
86 let abs_str = path
87 .canonicalize()
88 .unwrap_or_else(|_| path.to_path_buf())
89 .to_string_lossy()
90 .to_string();
91 out.push_str(&format!(
92 "::anvilforge::inventory::submit! {{ ::anvilforge::spark::template::EmbeddedTemplate {{ view_path: {view:?}, source: include_str!({src:?}) }} }}\n",
93 view = view_path,
94 src = abs_str,
95 ));
96 }
97
98 fs::write(output_rs, out)?;
99 Ok(())
100}
101
102pub fn compile_dir(input_dir: &Path, output_dir: &Path) -> std::io::Result<Vec<PathBuf>> {
105 let mut written = Vec::new();
106 if !input_dir.exists() {
107 return Ok(written);
108 }
109 for entry in WalkDir::new(input_dir).into_iter().filter_map(|e| e.ok()) {
110 if !entry.file_type().is_file() {
111 continue;
112 }
113 let path = entry.path();
114 let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
115 continue;
116 };
117 if !file_name.ends_with(".forge.html") && !file_name.ends_with(".forge") {
118 continue;
119 }
120 let rel = path.strip_prefix(input_dir).unwrap_or(path);
121 let out_name = file_name
122 .replace(".forge.html", ".html")
123 .replace(".forge", ".html");
124 let mut out_path = output_dir.to_path_buf();
125 if let Some(parent) = rel.parent() {
126 out_path.push(parent);
127 }
128 out_path.push(out_name);
129 compile_file(path, &out_path)?;
130 written.push(out_path);
131 }
132 Ok(written)
133}