use std::path::{Path, PathBuf};
use super::{generate_with, parse_xml};
pub const GENERATED_DIR: &str = ".yog-build/generated";
fn absolute(path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir().unwrap_or_default().join(path)
}
}
pub fn project_root(yui: &Path) -> Option<PathBuf> {
absolute(yui)
.ancestors()
.skip(1)
.find(|d| d.join(crate::manifest::MANIFEST).is_file())
.map(Path::to_path_buf)
}
pub fn include_name(root: &Path, yui: &Path) -> String {
let abs = absolute(yui);
let rel = abs
.strip_prefix(root.join(yog_ids::project::SRC))
.or_else(|_| abs.strip_prefix(root))
.unwrap_or(&abs)
.with_extension("");
rel.components()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join(yog_ids::sep::PATH)
}
pub fn generated_path(root: &Path, name: &str) -> PathBuf {
root.join(GENERATED_DIR)
.join(format!("{name}{GENERATED_EXT}"))
}
const RS: &str = "rs";
const GENERATED_EXT: &str = ".g.rs";
pub fn generate_file(root: &Path, yui: &Path) -> Result<(PathBuf, Vec<String>), String> {
let src = std::fs::read_to_string(yui).map_err(|e| format!("{}: {e}", yui.display()))?;
let name = include_name(root, yui);
let mod_id = std::fs::read_to_string(root.join(crate::manifest::MANIFEST))
.ok()
.and_then(|t| crate::manifest::parse_yog_toml(&t).ok())
.map(|t| t.id);
let layouts: Vec<yog_formats::book::layout::RecipeLayout> = if src.contains(RECIPE_AREA_TAG) {
crate::book_page::find_layouts(
root,
mod_id.as_deref().unwrap_or(yog_ids::ns::MINECRAFT),
&mut Vec::new(),
)
.into_iter()
.map(|(_, l)| l)
.collect()
} else {
Vec::new()
};
let (code, warnings) = generate_with(&src, &name, mod_id.as_deref(), &layouts)
.map_err(|e| format!("{}: {e}", yui.display()))?;
let out = generated_path(root, &name);
crate::write_if_changed(&out, &code)?;
Ok((
out,
warnings
.into_iter()
.map(|w| format!("{}: {w}", yui.display()))
.collect(),
))
}
const RECIPE_AREA_TAG: &str = yog_ids::concatcp!("<", yog_ids::yui::RECIPE_AREA);
pub fn is_book_page_file(path: &Path) -> bool {
std::fs::read_to_string(path)
.ok()
.and_then(|s| parse_xml(&s).ok())
.is_some_and(|r| crate::book_page::is_book_ui(&r))
}
pub fn find_yui_files(dir: &Path) -> Vec<PathBuf> {
crate::files::with_ext(dir, yog_ids::Format::Ui.ext())
}
pub struct Generated {
pub screens: usize,
pub warnings: Vec<String>,
}
pub fn generate_all(root: &Path) -> Result<Generated, String> {
let files = find_yui_files(&root.join(yog_ids::project::SRC));
let mut wanted = Vec::new();
let mut warnings = Vec::new();
for file in &files {
if is_book_page_file(file) {
continue;
}
let (out, w) = generate_file(root, file)?;
wanted.push(out);
warnings.extend(w);
}
let (index, warning) = crate::yui_component::write_index(root)?;
wanted.push(index);
warnings.extend(warning);
let res_index = root.join(crate::yres::GENERATED_FILE);
let res_dir = root.join(crate::yres::GENERATED_DIR);
let content_file = root.join(crate::content::GENERATED_FILE);
let content_parts = content_file
.parent()
.map(|d| d.join(crate::content::GENERATED_PARTS_DIR))
.unwrap_or_default();
for stale in crate::files::with_ext(&root.join(GENERATED_DIR), RS) {
if stale.to_string_lossy().ends_with(GENERATED_EXT)
&& !wanted.contains(&stale)
&& stale != res_index
&& stale != content_file
&& !stale.starts_with(&content_parts)
&& !stale.starts_with(&res_dir)
{
let _ = std::fs::remove_file(&stale);
}
}
Ok(Generated {
screens: files.len(),
warnings,
})
}