1use std::path::{Path, PathBuf};
5
6use super::{generate_with, parse_xml};
7
8pub const GENERATED_DIR: &str = ".yog-build/generated";
10
11fn absolute(path: &Path) -> PathBuf {
13 if path.is_absolute() {
14 path.to_path_buf()
15 } else {
16 std::env::current_dir().unwrap_or_default().join(path)
17 }
18}
19
20pub fn project_root(yui: &Path) -> Option<PathBuf> {
22 absolute(yui)
23 .ancestors()
24 .skip(1)
25 .find(|d| d.join(crate::manifest::MANIFEST).is_file())
26 .map(Path::to_path_buf)
27}
28
29pub fn include_name(root: &Path, yui: &Path) -> String {
32 let abs = absolute(yui);
33 let rel = abs
34 .strip_prefix(root.join(yog_ids::project::SRC))
35 .or_else(|_| abs.strip_prefix(root))
36 .unwrap_or(&abs)
37 .with_extension("");
38 rel.components()
39 .map(|c| c.as_os_str().to_string_lossy().into_owned())
40 .collect::<Vec<_>>()
41 .join(yog_ids::sep::PATH)
42}
43
44pub fn generated_path(root: &Path, name: &str) -> PathBuf {
47 root.join(GENERATED_DIR)
48 .join(format!("{name}{GENERATED_EXT}"))
49}
50
51const RS: &str = "rs";
53const GENERATED_EXT: &str = ".g.rs";
54
55pub fn generate_file(root: &Path, yui: &Path) -> Result<(PathBuf, Vec<String>), String> {
59 let src = std::fs::read_to_string(yui).map_err(|e| format!("{}: {e}", yui.display()))?;
60 let name = include_name(root, yui);
61 let mod_id = std::fs::read_to_string(root.join(crate::manifest::MANIFEST))
62 .ok()
63 .and_then(|t| crate::manifest::parse_yog_toml(&t).ok())
64 .map(|t| t.id);
65 let layouts: Vec<yog_formats::book::layout::RecipeLayout> = if src.contains(RECIPE_AREA_TAG) {
67 crate::book_page::find_layouts(
68 root,
69 mod_id.as_deref().unwrap_or(yog_ids::ns::MINECRAFT),
70 &mut Vec::new(),
71 )
72 .into_iter()
73 .map(|(_, l)| l)
74 .collect()
75 } else {
76 Vec::new()
77 };
78 let (code, warnings) = generate_with(&src, &name, mod_id.as_deref(), &layouts)
79 .map_err(|e| format!("{}: {e}", yui.display()))?;
80 let out = generated_path(root, &name);
81 crate::write_if_changed(&out, &code)?;
82 Ok((
83 out,
84 warnings
85 .into_iter()
86 .map(|w| format!("{}: {w}", yui.display()))
87 .collect(),
88 ))
89}
90
91const RECIPE_AREA_TAG: &str = yog_ids::concatcp!("<", yog_ids::yui::RECIPE_AREA);
93
94pub fn is_book_page_file(path: &Path) -> bool {
96 std::fs::read_to_string(path)
97 .ok()
98 .and_then(|s| parse_xml(&s).ok())
99 .is_some_and(|r| crate::book_page::is_book_ui(&r))
100}
101
102pub fn find_yui_files(dir: &Path) -> Vec<PathBuf> {
104 crate::files::with_ext(dir, yog_ids::Format::Ui.ext())
105}
106
107pub struct Generated {
109 pub screens: usize,
110 pub warnings: Vec<String>,
111}
112
113pub fn generate_all(root: &Path) -> Result<Generated, String> {
116 let files = find_yui_files(&root.join(yog_ids::project::SRC));
117 let mut wanted = Vec::new();
118 let mut warnings = Vec::new();
119 for file in &files {
120 if is_book_page_file(file) {
122 continue;
123 }
124 let (out, w) = generate_file(root, file)?;
125 wanted.push(out);
126 warnings.extend(w);
127 }
128 let (index, warning) = crate::yui_component::write_index(root)?;
130 wanted.push(index);
131 warnings.extend(warning);
132 let res_index = root.join(crate::yres::GENERATED_FILE);
134 let res_dir = root.join(crate::yres::GENERATED_DIR);
135 let content_file = root.join(crate::content::GENERATED_FILE);
137 let content_parts = content_file
138 .parent()
139 .map(|d| d.join(crate::content::GENERATED_PARTS_DIR))
140 .unwrap_or_default();
141 for stale in crate::files::with_ext(&root.join(GENERATED_DIR), RS) {
142 if stale.to_string_lossy().ends_with(GENERATED_EXT)
143 && !wanted.contains(&stale)
144 && stale != res_index
145 && stale != content_file
146 && !stale.starts_with(&content_parts)
147 && !stale.starts_with(&res_dir)
148 {
149 let _ = std::fs::remove_file(&stale);
150 }
151 }
152 Ok(Generated {
153 screens: files.len(),
154 warnings,
155 })
156}