use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Deserialize)]
pub struct Config {
#[allow(dead_code)]
pub meta: Meta,
#[serde(default)]
pub rings: BTreeMap<String, Ring>,
#[serde(default)]
pub sources: BTreeMap<String, String>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct Meta {
pub name: String,
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct Ring {
pub name: String,
pub description: String,
#[serde(default)]
pub terms: Vec<String>,
}
impl Config {
pub fn load(path: &Path) -> Result<Self, String> {
let content = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {e}", path.display()))?;
toml::from_str(&content).map_err(|e| format!("Failed to parse {}: {e}", path.display()))
}
pub fn get_ring(&self, level: u32) -> Option<&Ring> {
self.rings.get(&level.to_string())
}
pub fn rings_sorted(&self) -> Vec<(u32, &Ring)> {
let mut result: Vec<(u32, &Ring)> = self
.rings
.iter()
.filter_map(|(k, v)| k.parse::<u32>().ok().map(|n| (n, v)))
.collect();
result.sort_by_key(|(n, _)| *n);
result
}
}
pub fn resolve_ontology_dir(explicit: Option<&Path>) -> Result<PathBuf, String> {
if let Some(p) = explicit {
let p = p.to_path_buf();
if p.join("existence.toml").exists() || p.join("src").is_dir() {
return Ok(p);
}
return Err(format!(
"Specified ontology path {} does not look like an ontology directory",
p.display()
));
}
let cwd = std::env::current_dir().map_err(|e| format!("Cannot get cwd: {e}"))?;
if cwd.join("existence.toml").exists() {
return Ok(cwd);
}
let home = home_dir()?;
let sources_dir = home.join(".existence").join("sources");
if sources_dir.is_dir() {
if let Ok(entries) = std::fs::read_dir(&sources_dir) {
for entry in entries.flatten() {
let p = entry.path();
if p.is_dir() {
if let Ok(inner) = std::fs::read_dir(&p) {
for inner_entry in inner.flatten() {
let ip = inner_entry.path();
if ip.is_dir()
&& (ip.join("existence.toml").exists() || ip.join("src").is_dir())
{
return Ok(ip);
}
}
}
if p.join("existence.toml").exists() || p.join("src").is_dir() {
return Ok(p);
}
}
}
}
}
Err(
"Cannot find ontology directory. Use --ontology <path>, cd into an ontology, or run `existence fetch`."
.to_string(),
)
}
pub fn home_dir() -> Result<PathBuf, String> {
std::env::var("HOME")
.map(PathBuf::from)
.map_err(|_| "HOME environment variable not set".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_config() {
let toml_str = r#"
[meta]
name = "existential-kernel/ontology"
description = "Reference existential ontology"
[rings.0]
name = "kernel"
description = "Universal terms, always loaded"
terms = ["existence", "entity", "abstraction"]
[rings.1]
name = "software"
description = "Software engineering bridge"
terms = ["project", "model"]
[sources]
upstream = "github:existential-kernel/ontology"
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.meta.name, "existential-kernel/ontology");
assert_eq!(config.rings.len(), 2);
assert_eq!(config.get_ring(0).unwrap().name, "kernel");
assert_eq!(config.get_ring(0).unwrap().terms.len(), 3);
assert_eq!(config.get_ring(1).unwrap().name, "software");
assert_eq!(config.get_ring(1).unwrap().terms.len(), 2);
assert_eq!(
config.sources["upstream"],
"github:existential-kernel/ontology"
);
}
#[test]
fn test_parse_config_no_sources() {
let toml_str = r#"
[meta]
name = "test"
description = "test ontology"
[rings.0]
name = "kernel"
description = "core"
terms = ["existence"]
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert!(config.sources.is_empty());
assert_eq!(config.rings.len(), 1);
}
#[test]
fn test_rings_sorted() {
let toml_str = r#"
[meta]
name = "test"
description = "test"
[rings.1]
name = "software"
description = "bridge"
terms = ["project"]
[rings.0]
name = "kernel"
description = "core"
terms = ["existence"]
"#;
let config: Config = toml::from_str(toml_str).unwrap();
let sorted = config.rings_sorted();
assert_eq!(sorted[0].0, 0);
assert_eq!(sorted[0].1.name, "kernel");
assert_eq!(sorted[1].0, 1);
assert_eq!(sorted[1].1.name, "software");
}
#[test]
fn test_resolve_explicit_path() {
let tmp = tempfile::tempdir().unwrap();
std::fs::create_dir_all(tmp.path().join("src")).unwrap();
std::fs::write(
tmp.path().join("existence.toml"),
"[meta]\nname = \"t\"\ndescription = \"t\"\n",
)
.unwrap();
let result = resolve_ontology_dir(Some(tmp.path()));
assert!(result.is_ok());
assert_eq!(result.unwrap(), tmp.path());
}
}