1use std::path::{Path, PathBuf};
5
6use super::{generate_with, parse_xml};
7use yog_data::Diagnostic;
8
9pub const GENERATED_DIR: &str = yog_ids::project::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::project::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| {
62 d.in_file(&shown)
63 .or_code(yog_data::Atom::new(yog_ids::build_problem::SCREEN))
64 };
65 let src = std::fs::read_to_string(yui).map_err(|e| screen(e.to_string().into()))?;
66 let name = include_name(root, yui);
67 let mod_id = std::fs::read_to_string(root.join(crate::project::manifest::MANIFEST))
68 .ok()
69 .and_then(|t| crate::project::manifest::parse_yog_toml(&t).ok())
70 .map(|t| t.id);
71 let layouts: Vec<yog_formats::book::layout::RecipeLayout> = if src.contains(RECIPE_AREA_TAG) {
73 crate::ui::book_page::find_layouts(
74 root,
75 mod_id.as_deref().unwrap_or(yog_ids::ns::MINECRAFT),
76 &mut Vec::new(),
77 )
78 .into_iter()
79 .map(|(_, l)| l)
80 .collect()
81 } else {
82 Vec::new()
83 };
84 let (code, warnings) =
85 generate_with(&src, &name, mod_id.as_deref(), &layouts).map_err(screen)?;
86 let out = generated_path(root, &name);
87 crate::write_if_changed(&out, &code)?;
88 Ok((out, warnings.into_iter().map(screen).collect()))
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::ui::book_page::is_book_ui(&r))
100}
101
102pub fn find_yui_files(dir: &Path) -> Vec<PathBuf> {
104 yog_analysis::files::with_ext(dir, &[yog_ids::Format::Ui.ext()])
105}
106
107pub struct Generated {
109 pub screens: usize,
111 pub warnings: Vec<Diagnostic>,
113}
114
115pub fn generate_all(root: &Path) -> Result<Generated, Diagnostic> {
118 let files = find_yui_files(&root.join(yog_ids::project::SRC));
119 let mut wanted = Vec::new();
120 let mut warnings = Vec::new();
121 for file in &files {
122 if is_book_page_file(file) {
124 continue;
125 }
126 let (out, w) = generate_file(root, file)?;
127 wanted.push(out);
128 warnings.extend(w);
129 }
130 let (index, warning) = crate::ui::component::write_index(root)?;
132 wanted.push(index);
133 warnings.extend(
134 warning
135 .map(|w| Diagnostic::warning(yog_data::Atom::new(yog_ids::build_problem::SCREEN), w)),
136 );
137 let res_index = root.join(crate::yres::GENERATED_FILE);
139 let res_dir = root.join(crate::yres::GENERATED_DIR);
140 let content_file = root.join(crate::content::GENERATED_FILE);
142 let content_parts = content_file
143 .parent()
144 .map(|d| d.join(crate::content::GENERATED_PARTS_DIR))
145 .unwrap_or_default();
146 for stale in yog_analysis::files::with_ext(&root.join(GENERATED_DIR), &[RS]) {
147 if stale.to_string_lossy().ends_with(GENERATED_EXT)
148 && !wanted.contains(&stale)
149 && stale != res_index
150 && stale != content_file
151 && stale != crate::project::script_lib::root_file(root)
152 && !stale.starts_with(&content_parts)
153 && !stale.starts_with(&res_dir)
154 {
155 crate::files::remove(&stale);
156 }
157 }
158 Ok(Generated {
159 screens: files.len(),
160 warnings,
161 })
162}