1pub mod codegen;
2pub mod config;
3pub mod parser;
4
5pub use parser::parse;
6
7use anyhow::{Context, Result};
8use std::path::Path;
9
10pub fn generate_all(ui_dir: &Path, out_file: &Path) -> Result<()> {
13 let mut output = String::new();
14 output.push_str("// Generated by fncc-core. Do not edit.\n\n");
15
16 let mut file_id = 0;
17 let entries = std::fs::read_dir(ui_dir).context("failed to read ui dir")?;
18 for entry in entries {
19 let entry = entry.context("failed to read entry")?;
20 let path = entry.path();
21 if path.extension().is_some_and(|ext| ext == "fui") {
22 let source = std::fs::read_to_string(&path).with_context(|| format!("failed to read {:?}", path))?;
23 let ast = parser::parse(&source).map_err(|e| anyhow::anyhow!("failed to parse {:?}: {e}", path))?;
24 let generated = codegen::generate_with_id(&ast, file_id);
25 output.push_str(&generated);
26 output.push('\n');
27 file_id += 1;
28 }
29 }
30
31 std::fs::write(out_file, &output).context("failed to write generated file")?;
32 Ok(())
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use std::path::PathBuf;
39 use std::sync::atomic::{AtomicUsize, Ordering};
40
41 static LIB_TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
42
43 fn test_dir() -> (PathBuf, PathBuf) {
44 let id = LIB_TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
45 let dir = std::env::temp_dir().join(format!("fncc_lib_test_{id}"));
46 let ui_dir = dir.join("ui");
47 std::fs::create_dir_all(&ui_dir).unwrap();
48 (dir, ui_dir)
49 }
50
51 #[test]
52 fn test_generate_all_creates_output_file() {
53 let (dir, ui_dir) = test_dir();
54 std::fs::write(ui_dir.join("App.fui"), "<Text>hello</Text>").unwrap();
55 let out_file = dir.join("out.rs");
56
57 generate_all(&ui_dir, &out_file).unwrap();
58
59 assert!(out_file.exists());
60 let content = std::fs::read_to_string(&out_file).unwrap();
61 assert!(content.contains("// Generated by fncc-core. Do not edit."));
62 assert!(content.contains("pub fn render_text()"));
63 std::fs::remove_dir_all(&dir).unwrap();
64 }
65
66 #[test]
67 fn test_generate_all_ignores_non_fui_files() {
68 let (dir, ui_dir) = test_dir();
69 std::fs::write(ui_dir.join("App.fui"), "<A></A>").unwrap();
70 std::fs::write(ui_dir.join("notes.txt"), "not a fui").unwrap();
71 std::fs::write(ui_dir.join("main.rs"), "fn main() {}").unwrap();
72 let out_file = dir.join("out.rs");
73
74 generate_all(&ui_dir, &out_file).unwrap();
75
76 let content = std::fs::read_to_string(&out_file).unwrap();
77 assert!(content.contains("render_a"));
78 assert!(!content.contains("notes"));
79 assert!(!content.contains("main"));
80 std::fs::remove_dir_all(&dir).unwrap();
81 }
82}