use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::Serialize;
pub fn relative_path(file: &Path, input_root: &Path) -> PathBuf {
file.strip_prefix(input_root).unwrap_or(file).to_path_buf()
}
pub fn read_file_lossy(path: &Path) -> Result<String> {
let bytes = fs::read(path).with_context(|| format!("reading {:?}", path))?;
if let Ok(s) = String::from_utf8(bytes.clone()) {
return Ok(s);
}
Ok(bytes.iter().map(|&b| b as char).collect())
}
pub fn parse_inf_file(path: &Path) -> Result<Vec<inf2ron::InfEntry>> {
let content = read_file_lossy(path)?;
inf2ron::InfParser::parse_str(&content).map_err(|e| anyhow::anyhow!("{}", e))
}
pub fn write_ron<T: Serialize + ?Sized>(path: &Path, data: &T) -> Result<()> {
let ron_str = ron::to_string(data)?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, &ron_str)?;
Ok(())
}
pub fn copy_to_staging(file: &Path, input_root: &Path, stage: &Path) -> Result<()> {
let relative = relative_path(file, input_root);
let dest = stage.join(relative);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)?;
}
fs::copy(file, &dest)?;
Ok(())
}
pub fn normalize_id(name: &str) -> String {
let mut out = String::with_capacity(name.len());
let mut prev_was_space = false;
for ch in name.chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch.to_ascii_lowercase());
prev_was_space = false;
} else if ch == '_' || ch == '-' {
out.push(ch);
prev_was_space = false;
} else if ch.is_ascii_whitespace() {
if !prev_was_space {
out.push('_');
prev_was_space = true;
}
} else {
if !prev_was_space && !out.is_empty() {
out.push('_');
prev_was_space = true;
}
}
}
let trimmed = out.trim_end_matches('_').to_string();
if trimmed.is_empty() { "content".to_string() } else { trimmed }
}