use anyhow::Result;
use heck::ToSnakeCase;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
pub fn discover_entities(project_root: &Path) -> Result<Vec<String>> {
let entities_dir = project_root.join("backend/src/entities");
let mut entities = Vec::new();
if !entities_dir.exists() {
return Ok(entities);
}
for entry in fs::read_dir(&entities_dir)? {
let entry = entry?;
let path = entry.path();
if let Some(ext) = path.extension() {
if ext == "rs" {
if let Some(stem) = path.file_stem() {
let name = stem.to_string_lossy().to_string();
if name != "mod" {
entities.push(name);
}
}
}
}
}
Ok(entities)
}
pub fn entity_exists(project_root: &Path, entity_name: &str) -> bool {
let snake = entity_name.to_snake_case();
project_root
.join(format!("backend/src/entities/{}.rs", snake))
.exists()
}
pub fn junction_name(entity_a: &str, entity_b: &str) -> String {
let a = entity_a.to_snake_case();
let b = entity_b.to_snake_case();
if a < b {
format!("{}_{}", a, b)
} else {
format!("{}_{}", b, a)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingRelation {
pub source_entity: String,
pub target_entity: String,
pub relation_type: String,
}
const PENDING_FILE: &str = ".romance/pending_relations.json";
pub fn store_pending(project_root: &Path, pending: PendingRelation) -> Result<()> {
let path = project_root.join(PENDING_FILE);
let mut pendings = load_pending(project_root)?;
let already = pendings.iter().any(|p| {
p.source_entity == pending.source_entity
&& p.target_entity == pending.target_entity
&& p.relation_type == pending.relation_type
});
if already {
return Ok(());
}
pendings.push(pending);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&path, serde_json::to_string_pretty(&pendings)?)?;
Ok(())
}
pub fn load_pending(project_root: &Path) -> Result<Vec<PendingRelation>> {
let path = project_root.join(PENDING_FILE);
if !path.exists() {
return Ok(Vec::new());
}
let content = fs::read_to_string(&path)?;
let pendings: Vec<PendingRelation> = serde_json::from_str(&content)?;
Ok(pendings)
}
pub fn take_pending_for(project_root: &Path, target_entity: &str) -> Result<Vec<PendingRelation>> {
let mut all = load_pending(project_root)?;
let target_snake = target_entity.to_snake_case();
let (matched, remaining): (Vec<_>, Vec<_>) = all.drain(..).partition(|p| {
p.target_entity.to_snake_case() == target_snake
});
if !matched.is_empty() {
let path = project_root.join(PENDING_FILE);
if remaining.is_empty() {
let _ = fs::remove_file(&path);
} else {
fs::write(&path, serde_json::to_string_pretty(&remaining)?)?;
}
}
Ok(matched)
}