1use clap_noun_verb::Result as VerbResult;
12use clap_noun_verb_macros::verb;
13use ggen_core::ontology::{CoreOntologyBundle, OntologyLoader};
14use ggen_core::validation::StandardOntology;
15use serde::Serialize;
16use std::collections::BTreeMap;
17use std::str::FromStr;
18
19#[derive(Debug, Clone, Serialize)]
25pub struct OntologyListOutput {
26 pub ontologies: Vec<OntologyListEntry>,
28 pub count: usize,
30}
31
32#[derive(Debug, Clone, Serialize)]
34pub struct OntologyListEntry {
35 pub name: String,
37 pub namespace: String,
39 pub size: usize,
41}
42
43#[derive(Debug, Clone, Serialize)]
45pub struct OntologyStatusOutput {
46 pub uri: String,
48 pub embedded: bool,
50 pub location: String,
52 pub size: Option<usize>,
54 pub name: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize)]
60pub struct OntologyInfoOutput {
61 pub name: String,
63 pub namespace: String,
65 pub size: usize,
67 pub embedded: bool,
69 pub hash: Option<String>,
71 pub metadata: BTreeMap<String, String>,
73}
74
75#[derive(Debug, Clone, Serialize)]
77pub struct OntologySearchOutput {
78 pub query: String,
80 pub results: Vec<OntologySearchResult>,
82 pub count: usize,
84 pub message: Option<String>,
86}
87
88#[derive(Debug, Clone, Serialize)]
90pub struct OntologySearchResult {
91 pub name: String,
93 pub description: String,
95 pub domain: String,
97}
98
99#[derive(Debug, Clone, Serialize)]
101pub struct OntologyInstallOutput {
102 pub package: String,
104 pub success: bool,
106 pub message: String,
108 pub size_bytes: Option<u64>,
110 pub digest: Option<String>,
112 pub dependencies_count: usize,
114}
115
116#[derive(Debug, Clone, Serialize)]
118pub struct OntologyLockOutput {
119 pub lock_file: String,
121 pub packages_count: usize,
123 pub total_size_bytes: u64,
125 pub message: String,
127 pub packages: Vec<LockFileEntry>,
129}
130
131#[derive(Debug, Clone, Serialize)]
133pub struct LockFileEntry {
134 pub id: String,
136 pub version: String,
138 pub digest: String,
140 pub installed_at: String,
142}
143
144#[derive(Debug, Clone, Serialize)]
146pub struct NamespacesListOutput {
147 pub namespaces: Vec<NamespaceEntry>,
149 pub count: usize,
151}
152
153#[derive(Debug, Clone, Serialize)]
155pub struct NamespaceEntry {
156 pub prefix: String,
158 pub uri: String,
160 pub source: String,
162}
163
164#[verb]
174pub fn list(#[arg(default_value = "true")] embedded: bool) -> VerbResult<OntologyListOutput> {
175 if !embedded {
176 return Ok(OntologyListOutput {
177 ontologies: vec![],
178 count: 0,
179 });
180 }
181
182 let ontologies = CoreOntologyBundle::all();
183 let entries: Vec<OntologyListEntry> = ontologies
184 .iter()
185 .map(|ont| OntologyListEntry {
186 name: ont.name.to_string(),
187 namespace: ont.namespace.to_string(),
188 size: ont.size,
189 })
190 .collect();
191
192 let count = entries.len();
193 Ok(OntologyListOutput {
194 ontologies: entries,
195 count,
196 })
197}
198
199#[verb]
204pub fn namespaces() -> VerbResult<NamespacesListOutput> {
205 let standard_ns = ggen_core::domain::ontology::get_standard_namespaces();
206 let namespaces: Vec<NamespaceEntry> = standard_ns
207 .into_iter()
208 .map(|ns| NamespaceEntry {
209 prefix: ns.prefix,
210 uri: ns.uri,
211 source: ns.source,
212 })
213 .collect();
214
215 let count = namespaces.len();
216 Ok(NamespacesListOutput { namespaces, count })
217}
218
219#[verb]
225pub fn status(uri: String) -> VerbResult<OntologyStatusOutput> {
226 let is_embedded = OntologyLoader::is_embedded(&uri);
227
228 if is_embedded {
229 if let Some(metadata) = OntologyLoader::get_metadata(&uri) {
231 return Ok(OntologyStatusOutput {
232 uri: uri.clone(),
233 embedded: true,
234 location: "core-bundle".to_string(),
235 size: Some(metadata.size),
236 name: Some(metadata.name.to_string()),
237 });
238 }
239 }
240
241 if let Some(_content) = OntologyLoader::load_content(&uri, std::path::Path::new(".")) {
243 return Ok(OntologyStatusOutput {
244 uri: uri.clone(),
245 embedded: false,
246 location: "filesystem".to_string(),
247 size: None,
248 name: None,
249 });
250 }
251
252 Ok(OntologyStatusOutput {
254 uri: uri.clone(),
255 embedded: false,
256 location: "not-found".to_string(),
257 size: None,
258 name: None,
259 })
260}
261
262#[verb]
268pub fn info(uri: String) -> VerbResult<OntologyInfoOutput> {
269 if let Some(metadata) = OntologyLoader::get_metadata(&uri) {
271 let mut metadata_map = BTreeMap::new();
272 metadata_map.insert("content_length".to_string(), metadata.size.to_string());
273
274 return Ok(OntologyInfoOutput {
275 name: metadata.name.to_string(),
276 namespace: uri.clone(),
277 size: metadata.size,
278 embedded: true,
279 hash: None, metadata: metadata_map,
281 });
282 }
283
284 let mut metadata_map = BTreeMap::new();
286 metadata_map.insert("status".to_string(), "not found".to_string());
287
288 Ok(OntologyInfoOutput {
289 name: "unknown".to_string(),
290 namespace: uri.clone(),
291 size: 0,
292 embedded: false,
293 hash: None,
294 metadata: metadata_map,
295 })
296}
297
298#[verb]
307pub fn search(query: String) -> VerbResult<OntologySearchOutput> {
308 let message = Some(format!(
310 "Marketplace integration not yet available. Use 'ggen ontology list' to see embedded ontologies."
311 ));
312
313 Ok(OntologySearchOutput {
314 query,
315 results: vec![],
316 count: 0,
317 message,
318 })
319}
320
321#[verb]
334pub fn install(package: String) -> VerbResult<OntologyInstallOutput> {
335 let parts: Vec<&str> = package.split('@').collect();
337 if parts.len() != 2 {
338 return Ok(OntologyInstallOutput {
339 package: package.clone(),
340 success: false,
341 message: "Error: Package format must be 'id@version' (e.g., 'acme/base@1.2.3')"
342 .to_string(),
343 size_bytes: None,
344 digest: None,
345 dependencies_count: 0,
346 });
347 }
348
349 let package_id = parts[0];
350 let version = parts[1];
351
352 Ok(OntologyInstallOutput {
357 package: package.clone(),
358 success: true,
359 message: format!(
360 "Ontology package {}@{} installed successfully (Phase 4 placeholder)",
361 package_id, version
362 ),
363 size_bytes: Some(1024 * 1024), digest: Some("abc123def456".to_string()),
365 dependencies_count: 0,
366 })
367}
368
369#[verb]
380pub fn lock() -> VerbResult<OntologyLockOutput> {
381 Ok(OntologyLockOutput {
389 lock_file: ".ggen/ontology.lock".to_string(),
390 packages_count: 0,
391 total_size_bytes: 0,
392 message: "Lock file created successfully (Phase 4 placeholder)".to_string(),
393 packages: vec![],
394 })
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_namespaces_command() {
403 let result = namespaces().expect("namespaces command failed");
404 assert_eq!(
405 result.count, 8,
406 "Expected 8 unique namespaces, got: {:?}",
407 result.namespaces
408 );
409 assert_eq!(result.namespaces.len(), 8);
410
411 let prefixes: std::collections::HashSet<&str> = result
413 .namespaces
414 .iter()
415 .map(|ns| ns.prefix.as_str())
416 .collect();
417 let expected = [
418 "rdf", "rdfs", "owl", "schema", "foaf", "dc", "skos", "bigfive",
419 ];
420 for p in &expected {
421 assert!(prefixes.contains(p), "Missing prefix: {}", p);
422 }
423
424 for ns in &result.namespaces {
426 match ns.prefix.as_str() {
427 "foaf" => {
428 assert_eq!(ns.uri, "http://xmlns.com/foaf/0.1/");
429 assert_eq!(ns.source, "bundled-standard");
430 }
431 "dc" => {
432 assert_eq!(ns.uri, "http://purl.org/dc/elements/1.1/");
433 assert_eq!(ns.source, "bundled-standard");
434 }
435 "rdf" => {
436 assert_eq!(ns.uri, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
437 assert_eq!(ns.source, "bundled-standard");
438 }
439 _ => {}
440 }
441 }
442 }
443}