ggen_cli_lib/
lib.rs

1//! # ggen-cli - Command-line interface for ggen code generation
2//!
3//! This crate provides the command-line interface for ggen, using clap-noun-verb
4//! for automatic command discovery and routing. It bridges between user commands
5//! and the domain logic layer (ggen-domain).
6//!
7//! ## Architecture
8//!
9//! - **Command Discovery**: Uses clap-noun-verb v3.4.0 auto-discovery to find
10//!   all `\[verb\]` functions in the `cmds` module
11//! - **Async/Sync Bridge**: Provides runtime utilities to bridge async domain
12//!   functions with synchronous CLI execution
13//! - **Conventions**: File-based routing conventions for template-based command
14//!   generation
15//! - **Node Integration**: Programmatic entry point for Node.js addon integration
16//!
17//! ## Features
18//!
19//! - **Auto-discovery**: Commands are automatically discovered via clap-noun-verb
20//! - **Version handling**: Built-in `--version` flag support
21//! - **Output capture**: Programmatic execution with stdout/stderr capture
22//! - **Async support**: Full async/await support for non-blocking operations
23//!
24//! ## Examples
25//!
26//! ### Basic CLI Execution
27//!
28//! ```rust,no_run
29//! use ggen_cli::cli_match;
30//!
31//! # async fn example() -> ggen_utils::error::Result<()> {
32//! // Execute CLI with auto-discovered commands
33//! cli_match().await?;
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! ### Programmatic Execution
39//!
40//! ```rust,no_run
41//! use ggen_cli::run_for_node;
42//!
43//! # async fn example() -> ggen_utils::error::Result<()> {
44//! let args = vec!["template".to_string(), "generate".to_string()];
45//! let result = run_for_node(args).await?;
46//! println!("Exit code: {}", result.code);
47//! println!("Output: {}", result.stdout);
48//! # Ok(())
49//! # }
50//! ```
51
52#![deny(warnings)] // Poka-Yoke: Prevent warnings at compile time - compiler enforces correctness
53#![allow(non_upper_case_globals)] // Allow macro-generated static variables from clap-noun-verb
54
55use std::io::{Read, Write};
56
57// Command modules - clap-noun-verb v3.4.0 auto-discovery
58pub mod cmds; // clap-noun-verb v3 entry points with #[verb] functions
59pub mod conventions; // File-based routing conventions
60                     // pub mod domain;          // Business logic layer - MOVED TO ggen-domain crate
61pub mod prelude;
62pub mod runtime; // Async/sync bridge utilities
63pub mod runtime_helper; // Sync CLI wrapper utilities for async operations // Common imports for commands
64
65// Re-export clap-noun-verb for auto-discovery
66pub use clap_noun_verb::{run, CommandRouter, Result as ClapNounVerbResult};
67
68/// Main entry point using clap-noun-verb v3.4.0 auto-discovery
69///
70/// This function delegates to clap-noun-verb::run() which automatically discovers
71/// all `\[verb\]` functions in the cmds module and its submodules.
72pub async fn cli_match() -> ggen_utils::error::Result<()> {
73    // Handle --version flag before delegating to clap-noun-verb
74    let args: Vec<String> = std::env::args().collect();
75    if args.iter().any(|arg| arg == "--version" || arg == "-V") {
76        log::info!("ggen {}", env!("CARGO_PKG_VERSION"));
77        return Ok(());
78    }
79
80    // Use clap-noun-verb auto-discovery
81    clap_noun_verb::run()
82        .map_err(|e| ggen_utils::error::Error::new(&format!("CLI execution failed: {}", e)))?;
83    Ok(())
84}
85
86/// Structured result for programmatic CLI execution (used by Node addon)
87#[derive(Debug, Clone)]
88pub struct RunResult {
89    pub code: i32,
90    pub stdout: String,
91    pub stderr: String,
92}
93
94/// Programmatic entrypoint to execute the CLI with provided arguments and capture output.
95/// This avoids spawning a new process and preserves deterministic behavior.
96pub async fn run_for_node(args: Vec<String>) -> ggen_utils::error::Result<RunResult> {
97    use std::sync::Arc;
98    use std::sync::Mutex;
99
100    // Prefix with a binary name to satisfy clap-noun-verb semantics
101    let _argv: Vec<String> = std::iter::once("ggen".to_string())
102        .chain(args.into_iter())
103        .collect();
104
105    // Create thread-safe buffers for capturing output
106    let stdout_buffer = Arc::new(Mutex::new(Vec::new()));
107    let stderr_buffer = Arc::new(Mutex::new(Vec::new()));
108
109    let stdout_clone = Arc::clone(&stdout_buffer);
110    let stderr_clone = Arc::clone(&stderr_buffer);
111
112    // Execute in a blocking task to avoid Send issues with gag
113    let result = tokio::task::spawn_blocking(move || {
114        // Capture stdout/stderr using gag buffers
115        let mut captured_stdout = Vec::new();
116        let mut captured_stderr = Vec::new();
117
118        let code = match (gag::BufferRedirect::stdout(), gag::BufferRedirect::stderr()) {
119            (Ok(mut so), Ok(mut se)) => {
120                // Execute using cmds router
121                let code_val = match cmds::run_cli() {
122                    Ok(()) => 0,
123                    Err(err) => {
124                        let _ = writeln!(std::io::stderr(), "{}", err);
125                        1
126                    }
127                };
128
129                let _ = so.read_to_end(&mut captured_stdout);
130                let _ = se.read_to_end(&mut captured_stderr);
131
132                *stdout_clone.lock().unwrap() = captured_stdout;
133                *stderr_clone.lock().unwrap() = captured_stderr;
134
135                code_val
136            }
137            _ => {
138                // Fallback: execute without capture
139                match cmds::run_cli() {
140                    Ok(()) => 0,
141                    Err(err) => {
142                        log::error!("{}", err);
143                        1
144                    }
145                }
146            }
147        };
148
149        code
150    })
151    .await
152    .map_err(|e| ggen_utils::error::Error::new(&format!("Failed to execute CLI: {}", e)))?;
153
154    let stdout = String::from_utf8_lossy(&stdout_buffer.lock().unwrap()).to_string();
155    let stderr = String::from_utf8_lossy(&stderr_buffer.lock().unwrap()).to_string();
156
157    Ok(RunResult {
158        code: result,
159        stdout,
160        stderr,
161    })
162}