use clap_noun_verb::Result as VerbResult;
use clap_noun_verb_macros::verb;
use ggen_core::ontology::{CoreOntologyBundle, OntologyLoader};
use ggen_core::validation::StandardOntology;
use serde::Serialize;
use std::collections::BTreeMap;
use std::str::FromStr;
#[derive(Debug, Clone, Serialize)]
pub struct OntologyListOutput {
pub ontologies: Vec<OntologyListEntry>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct OntologyListEntry {
pub name: String,
pub namespace: String,
pub size: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct OntologyStatusOutput {
pub uri: String,
pub embedded: bool,
pub location: String,
pub size: Option<usize>,
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OntologyInfoOutput {
pub name: String,
pub namespace: String,
pub size: usize,
pub embedded: bool,
pub hash: Option<String>,
pub metadata: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OntologySearchOutput {
pub query: String,
pub results: Vec<OntologySearchResult>,
pub count: usize,
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OntologySearchResult {
pub name: String,
pub description: String,
pub domain: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct OntologyInstallOutput {
pub package: String,
pub success: bool,
pub message: String,
pub size_bytes: Option<u64>,
pub digest: Option<String>,
pub dependencies_count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct OntologyLockOutput {
pub lock_file: String,
pub packages_count: usize,
pub total_size_bytes: u64,
pub message: String,
pub packages: Vec<LockFileEntry>,
}
#[derive(Debug, Clone, Serialize)]
pub struct LockFileEntry {
pub id: String,
pub version: String,
pub digest: String,
pub installed_at: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct NamespacesListOutput {
pub namespaces: Vec<NamespaceEntry>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct NamespaceEntry {
pub prefix: String,
pub uri: String,
pub source: String,
}
#[verb]
pub fn list(#[arg(default_value = "true")] embedded: bool) -> VerbResult<OntologyListOutput> {
if !embedded {
return Ok(OntologyListOutput {
ontologies: vec![],
count: 0,
});
}
let ontologies = CoreOntologyBundle::all();
let entries: Vec<OntologyListEntry> = ontologies
.iter()
.map(|ont| OntologyListEntry {
name: ont.name.to_string(),
namespace: ont.namespace.to_string(),
size: ont.size,
})
.collect();
let count = entries.len();
Ok(OntologyListOutput {
ontologies: entries,
count,
})
}
#[verb]
pub fn namespaces() -> VerbResult<NamespacesListOutput> {
let standard_ns = ggen_core::domain::ontology::get_standard_namespaces();
let namespaces: Vec<NamespaceEntry> = standard_ns
.into_iter()
.map(|ns| NamespaceEntry {
prefix: ns.prefix,
uri: ns.uri,
source: ns.source,
})
.collect();
let count = namespaces.len();
Ok(NamespacesListOutput { namespaces, count })
}
#[verb]
pub fn status(uri: String) -> VerbResult<OntologyStatusOutput> {
let is_embedded = OntologyLoader::is_embedded(&uri);
if is_embedded {
if let Some(metadata) = OntologyLoader::get_metadata(&uri) {
return Ok(OntologyStatusOutput {
uri: uri.clone(),
embedded: true,
location: "core-bundle".to_string(),
size: Some(metadata.size),
name: Some(metadata.name.to_string()),
});
}
}
if let Some(_content) = OntologyLoader::load_content(&uri, std::path::Path::new(".")) {
return Ok(OntologyStatusOutput {
uri: uri.clone(),
embedded: false,
location: "filesystem".to_string(),
size: None,
name: None,
});
}
Ok(OntologyStatusOutput {
uri: uri.clone(),
embedded: false,
location: "not-found".to_string(),
size: None,
name: None,
})
}
#[verb]
pub fn info(uri: String) -> VerbResult<OntologyInfoOutput> {
if let Some(metadata) = OntologyLoader::get_metadata(&uri) {
let mut metadata_map = BTreeMap::new();
metadata_map.insert("content_length".to_string(), metadata.size.to_string());
return Ok(OntologyInfoOutput {
name: metadata.name.to_string(),
namespace: uri.clone(),
size: metadata.size,
embedded: true,
hash: None, metadata: metadata_map,
});
}
let mut metadata_map = BTreeMap::new();
metadata_map.insert("status".to_string(), "not found".to_string());
Ok(OntologyInfoOutput {
name: "unknown".to_string(),
namespace: uri.clone(),
size: 0,
embedded: false,
hash: None,
metadata: metadata_map,
})
}
#[verb]
pub fn search(query: String) -> VerbResult<OntologySearchOutput> {
let message = Some(format!(
"Marketplace integration not yet available. Use 'ggen ontology list' to see embedded ontologies."
));
Ok(OntologySearchOutput {
query,
results: vec![],
count: 0,
message,
})
}
#[verb]
pub fn install(package: String) -> VerbResult<OntologyInstallOutput> {
let parts: Vec<&str> = package.split('@').collect();
if parts.len() != 2 {
return Ok(OntologyInstallOutput {
package: package.clone(),
success: false,
message: "Error: Package format must be 'id@version' (e.g., 'acme/base@1.2.3')"
.to_string(),
size_bytes: None,
digest: None,
dependencies_count: 0,
});
}
let package_id = parts[0];
let version = parts[1];
Ok(OntologyInstallOutput {
package: package.clone(),
success: true,
message: format!(
"Ontology package {}@{} installed successfully (Phase 4 placeholder)",
package_id, version
),
size_bytes: Some(1024 * 1024), digest: Some("abc123def456".to_string()),
dependencies_count: 0,
})
}
#[verb]
pub fn lock() -> VerbResult<OntologyLockOutput> {
Ok(OntologyLockOutput {
lock_file: ".ggen/ontology.lock".to_string(),
packages_count: 0,
total_size_bytes: 0,
message: "Lock file created successfully (Phase 4 placeholder)".to_string(),
packages: vec![],
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_namespaces_command() {
let result = namespaces().expect("namespaces command failed");
assert_eq!(
result.count, 8,
"Expected 8 unique namespaces, got: {:?}",
result.namespaces
);
assert_eq!(result.namespaces.len(), 8);
let prefixes: std::collections::HashSet<&str> = result
.namespaces
.iter()
.map(|ns| ns.prefix.as_str())
.collect();
let expected = [
"rdf", "rdfs", "owl", "schema", "foaf", "dc", "skos", "bigfive",
];
for p in &expected {
assert!(prefixes.contains(p), "Missing prefix: {}", p);
}
for ns in &result.namespaces {
match ns.prefix.as_str() {
"foaf" => {
assert_eq!(ns.uri, "http://xmlns.com/foaf/0.1/");
assert_eq!(ns.source, "bundled-standard");
}
"dc" => {
assert_eq!(ns.uri, "http://purl.org/dc/elements/1.1/");
assert_eq!(ns.source, "bundled-standard");
}
"rdf" => {
assert_eq!(ns.uri, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
assert_eq!(ns.source, "bundled-standard");
}
_ => {}
}
}
}
}