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