use serde::Deserialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub const FILE_NAME: &str = "rusteal.toml";
#[derive(Deserialize)]
pub struct ProjectConfig {
pub project: ProjectSection,
pub codegen: CodegenConfig,
}
#[derive(Deserialize)]
pub struct ProjectSection {
#[serde(rename = "crate")]
pub crate_name: String,
#[serde(default)]
pub features: Vec<String>,
}
#[derive(Deserialize)]
pub struct CodegenConfig {
pub features: Vec<String>,
pub modules: HashMap<String, ModuleMapping>,
#[serde(default)]
pub blocklist: Blocklist,
}
#[derive(Deserialize)]
pub struct ModuleMapping {
pub module: String,
pub feature: String,
}
#[derive(Deserialize, Default)]
pub struct Blocklist {
#[serde(default)]
pub classes: Vec<String>,
#[serde(default)]
pub structs: Vec<String>,
#[serde(default)]
pub functions: Vec<String>,
}
impl Blocklist {
pub fn function_tuples(&self) -> Vec<(String, String)> {
self.functions
.iter()
.filter_map(|entry| {
let (class, func) = entry.split_once('.')?;
Some((class.to_string(), func.to_string()))
})
.collect()
}
}
impl ProjectConfig {
pub fn load(root: &Path) -> Result<Self, String> {
let path = root.join(FILE_NAME);
let text = std::fs::read_to_string(&path)
.map_err(|e| format!("cannot read {}: {e}", path.display()))?;
toml::from_str(&text).map_err(|e| format!("cannot parse {}: {e}", path.display()))
}
}
pub struct ProjectLayout {
pub root: PathBuf,
}
impl ProjectLayout {
pub fn new(root: &Path) -> Self {
Self { root: root.to_path_buf() }
}
pub fn uproject(&self) -> Option<PathBuf> {
find_uproject(&self.root)
}
pub fn rust_workspace(&self) -> PathBuf {
self.root.join("Rust")
}
pub fn bindings_crate(&self) -> PathBuf {
self.rust_workspace().join("bindings")
}
pub fn cpp_generated(&self) -> PathBuf {
self.root.join("Plugins/Rusteal/Source/Rusteal/Generated")
}
pub fn uht_json(&self) -> PathBuf {
self.root.join("Intermediate/Rusteal/uht")
}
pub fn config_file(&self) -> PathBuf {
self.root.join(FILE_NAME)
}
}
pub fn find_uproject(dir: &Path) -> Option<PathBuf> {
std::fs::read_dir(dir)
.ok()?
.flatten()
.map(|entry| entry.path())
.find(|path| path.extension().is_some_and(|ext| ext == "uproject"))
}
pub fn find_project_root(start: &Path) -> Option<PathBuf> {
let start = start.canonicalize().ok()?;
start
.ancestors()
.find(|dir| find_uproject(dir).is_some())
.map(Path::to_path_buf)
}