ggen-cli-lib 26.7.3

CLI interface for ggen
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
//! Ontology Commands - Embedded and Marketplace Ontology Management
//!
//! This module implements ontology management commands for the ggen CLI:
//! - `ggen ontology list --embedded` - List embedded ontologies
//! - `ggen ontology status <uri>` - Check ontology availability
//! - `ggen ontology info <uri>` - Display detailed metadata
//! - `ggen ontology search <domain>` - Search for domain ontologies
//! - `ggen ontology install <package>` - Install ontology package from marketplace
//! - `ggen ontology lock` - Create lock file for installed packages

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;

// ============================================================================
// Output Types (all must derive Serialize for JSON output)
// ============================================================================

/// Output for the `ggen ontology list` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyListOutput {
    /// List of ontologies
    pub ontologies: Vec<OntologyListEntry>,
    /// Total count
    pub count: usize,
}

/// Single ontology entry for list output
#[derive(Debug, Clone, Serialize)]
pub struct OntologyListEntry {
    /// Ontology name
    pub name: String,
    /// Namespace URI
    pub namespace: String,
    /// Size in bytes
    pub size: usize,
}

/// Output for the `ggen ontology status` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyStatusOutput {
    /// URI being queried
    pub uri: String,
    /// Whether it's embedded in the core bundle
    pub embedded: bool,
    /// Location ("core-bundle", "filesystem", or "not-found")
    pub location: String,
    /// Size in bytes if found
    pub size: Option<usize>,
    /// Name if found
    pub name: Option<String>,
}

/// Output for the `ggen ontology info` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyInfoOutput {
    /// Ontology name
    pub name: String,
    /// Namespace URI
    pub namespace: String,
    /// Size in bytes
    pub size: usize,
    /// Whether embedded
    pub embedded: bool,
    /// Content hash if available
    pub hash: Option<String>,
    /// Metadata
    pub metadata: BTreeMap<String, String>,
}

/// Output for the `ggen ontology search` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologySearchOutput {
    /// Search query
    pub query: String,
    /// Results found
    pub results: Vec<OntologySearchResult>,
    /// Total count
    pub count: usize,
    /// Message (for placeholders)
    pub message: Option<String>,
}

/// Single search result entry
#[derive(Debug, Clone, Serialize)]
pub struct OntologySearchResult {
    /// Package name
    pub name: String,
    /// Brief description
    pub description: String,
    /// Domain category
    pub domain: String,
}

/// Output for the `ggen ontology install` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyInstallOutput {
    /// Package ID installed (e.g., "acme/base@1.2.3")
    pub package: String,
    /// Whether installation succeeded
    pub success: bool,
    /// Installation message
    pub message: String,
    /// Package size in bytes
    pub size_bytes: Option<u64>,
    /// SHA-256 digest of the package
    pub digest: Option<String>,
    /// Number of dependencies installed
    pub dependencies_count: usize,
}

/// Output for the `ggen ontology lock` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyLockOutput {
    /// Lock file path
    pub lock_file: String,
    /// Number of packages locked
    pub packages_count: usize,
    /// Total size in bytes
    pub total_size_bytes: u64,
    /// Message describing the lock operation
    pub message: String,
    /// List of locked packages
    pub packages: Vec<LockFileEntry>,
}

/// Entry in a lock file
#[derive(Debug, Clone, Serialize)]
pub struct LockFileEntry {
    /// Package ID
    pub id: String,
    /// Package version
    pub version: String,
    /// SHA-256 digest
    pub digest: String,
    /// Installed timestamp
    pub installed_at: String,
}

/// Output for the `ggen ontology namespaces` command
#[derive(Debug, Clone, Serialize)]
pub struct NamespacesListOutput {
    /// List of namespace URIs and their prefixes
    pub namespaces: Vec<NamespaceEntry>,
    /// Total count
    pub count: usize,
}

/// Single namespace entry
#[derive(Debug, Clone, Serialize)]
pub struct NamespaceEntry {
    /// Prefix (e.g. rdf, owl, schema)
    pub prefix: String,
    /// Full namespace URI
    pub uri: String,
    /// Source (e.g. bundled-standard, core-stub)
    pub source: String,
}

// ============================================================================
// Verb Functions (the actual CLI commands)
// ============================================================================

/// List all embedded ontologies in the core bundle.
///
/// Usage:
///   ggen ontology list
///   ggen ontology list --embedded
#[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,
    })
}

/// List all available namespaces currently known to ggen
///
/// Usage:
///   ggen ontology namespaces
#[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 })
}

/// Check the status and availability of an ontology.
///
/// Usage:
///   ggen ontology status http://www.w3.org/1999/02/22-rdf-syntax-ns#
///   ggen ontology status <uri>
#[verb]
pub fn status(uri: String) -> VerbResult<OntologyStatusOutput> {
    let is_embedded = OntologyLoader::is_embedded(&uri);

    if is_embedded {
        // Get metadata from core bundle
        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()),
            });
        }
    }

    // Check if it exists on the filesystem
    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,
        });
    }

    // Not found
    Ok(OntologyStatusOutput {
        uri: uri.clone(),
        embedded: false,
        location: "not-found".to_string(),
        size: None,
        name: None,
    })
}

/// Display detailed metadata for an ontology.
///
/// Usage:
///   ggen ontology info http://www.w3.org/1999/02/22-rdf-syntax-ns#
///   ggen ontology info <uri>
#[verb]
pub fn info(uri: String) -> VerbResult<OntologyInfoOutput> {
    // Try to get metadata from core bundle
    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, // Hash could be computed if needed
            metadata: metadata_map,
        });
    }

    // Not found
    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,
    })
}

/// Search for ontologies by domain or keyword.
///
/// Usage:
///   ggen ontology search financial
///   ggen ontology search healthcare
///   ggen ontology search <domain>
///
/// Note: This is a placeholder for marketplace integration.
#[verb]
pub fn search(query: String) -> VerbResult<OntologySearchOutput> {
    // Placeholder: In Phase 4, this will query the marketplace
    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,
    })
}

/// Install an ontology package from the marketplace.
///
/// Usage:
///   ggen ontology install acme/base@1.2.3
///   ggen ontology install <package>@<version>
///
/// This command:
/// 1. Parses the package@version format
/// 2. Fetches metadata from the marketplace registry
/// 3. Resolves dependencies
/// 4. Downloads and caches packages
/// 5. Updates the lock file
#[verb]
pub fn install(package: String) -> VerbResult<OntologyInstallOutput> {
    // Parse package@version format
    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];

    // NOTE: Phase 4 implementation - this is a placeholder that shows the structure.
    // The actual marketplace client integration will be done with real HTTP calls (Chicago TDD)
    // in the full implementation using ggen_marketplace::MarketplaceClient.

    Ok(OntologyInstallOutput {
        package: package.clone(),
        success: true,
        message: format!(
            "Ontology package {}@{} installed successfully (Phase 4 placeholder)",
            package_id, version
        ),
        size_bytes: Some(1024 * 1024), // 1MB example
        digest: Some("abc123def456".to_string()),
        dependencies_count: 0,
    })
}

/// Create or update a lock file for installed ontologies.
///
/// Usage:
///   ggen ontology lock
///
/// This command:
/// 1. Scans installed packages or ggen.toml
/// 2. Computes SHA-256 digests for all packages
/// 3. Creates .ggen/lock file with deterministic entries
/// 4. Reports summary (count, total size, hashes)
#[verb]
pub fn lock() -> VerbResult<OntologyLockOutput> {
    // NOTE: Phase 4 implementation - this is a placeholder that shows the structure.
    // The actual lock file creation will be done with:
    // 1. Reading installed packages from cache or ggen.toml
    // 2. Computing SHA-256 hashes for each package
    // 3. Writing to .ggen/ontology.lock
    // 4. Deterministic ordering by package ID

    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);

        // Verify prefixes
        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);
        }

        // Verify some URIs and sources
        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");
                }
                _ => {}
            }
        }
    }
}