dynamic_cli/interface/mod.rs
1//! User interface module
2//!
3//! This module provides two main interfaces for interacting with the CLI framework:
4//!
5//! - [`CliInterface`]: One-shot command execution from command-line arguments
6//! - [`ReplInterface`]: Interactive REPL (Read-Eval-Print Loop) with history
7//!
8//! # Overview
9//!
10//! The `interface` module is the user-facing layer of the framework. It handles:
11//! - Parsing user input (CLI args or REPL lines)
12//! - Executing commands through the registry
13//! - Displaying results and errors
14//! - Managing command history (REPL only)
15//!
16//! # Choosing an Interface
17//!
18//! ## CLI Interface
19//!
20//! Use [`CliInterface`] when:
21//! - Running single commands from scripts
22//! - Building traditional CLI tools
23//! - No interaction is needed
24//! - Each invocation is independent
25//!
26//! ```no_run
27//! use dynamic_cli::interface::CliInterface;
28//! use dynamic_cli::prelude::*;
29//!
30//! # #[derive(Default)]
31//! # struct MyContext;
32//! # impl ExecutionContext for MyContext {
33//! # fn as_any(&self) -> &dyn std::any::Any { self }
34//! # fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
35//! # }
36//! # fn main() -> dynamic_cli::Result<()> {
37//! let registry = CommandRegistry::new();
38//! let context = Box::new(MyContext::default());
39//!
40//! let cli = CliInterface::new(registry, context);
41//! cli.run(std::env::args().skip(1).collect())?;
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! ## REPL Interface
47//!
48//! Use [`ReplInterface`] when:
49//! - Building interactive tools
50//! - Users need to run multiple commands
51//! - Context/state is preserved between commands
52//! - Command history and line editing are desired
53//!
54//! ```no_run
55//! use dynamic_cli::interface::ReplInterface;
56//! use dynamic_cli::prelude::*;
57//!
58//! # #[derive(Default)]
59//! # struct MyContext;
60//! # impl ExecutionContext for MyContext {
61//! # fn as_any(&self) -> &dyn std::any::Any { self }
62//! # fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
63//! # }
64//! # fn main() -> dynamic_cli::Result<()> {
65//! let registry = CommandRegistry::new();
66//! let context = Box::new(MyContext::default());
67//!
68//! let repl = ReplInterface::new(registry, context, "myapp".to_string(), None, None)?;
69//! repl.run()?; // Enters interactive loop
70//! # Ok(())
71//! # }
72//! ```
73//!
74//! # Architecture
75//!
76//! Both interfaces follow the same flow:
77//!
78//! ```text
79//! User Input → Parser → Validator → Executor → Handler
80//! ↓
81//! ExecutionContext
82//! ```
83//!
84//! **Key differences**:
85//!
86//! | Aspect | CLI | REPL |
87//! |--------|-----|------|
88//! | Input | Command-line args | Interactive lines |
89//! | Parser | [`CliParser`] | [`ReplParser`] |
90//! | History | None | Persistent to disk |
91//! | Errors | Exit process | Display and continue |
92//! | Lifecycle | One command, exit | Loop until user quits |
93//!
94//! # Error Handling
95//!
96//! ## CLI Interface
97//!
98//! Errors cause the process to exit with specific codes:
99//! - `0`: Success
100//! - `1`: Execution error
101//! - `2`: Parse/validation error
102//! - `3`: Other errors
103//!
104//! ## REPL Interface
105//!
106//! Errors are displayed but the REPL continues:
107//! - Parse errors → show suggestions, continue
108//! - Validation errors → explain issue, continue
109//! - Execution errors → display error, continue
110//! - Critical errors → exit REPL
111//!
112//! # Examples
113//!
114//! ## Complete CLI Application
115//!
116//! ```no_run
117//! use dynamic_cli::prelude::*;
118//! use dynamic_cli::config::loader::load_config;
119//! use dynamic_cli::interface::CliInterface;
120//!
121//! // Define context
122//! #[derive(Default)]
123//! struct AppContext {
124//! data: Vec<String>,
125//! }
126//!
127//! impl ExecutionContext for AppContext {
128//! fn as_any(&self) -> &dyn std::any::Any { self }
129//! fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
130//! }
131//!
132//! // Define handler
133//! struct AddCommand;
134//!
135//! impl CommandHandler for AddCommand {
136//! fn execute(
137//! &self,
138//! context: &mut dyn ExecutionContext,
139//! args: &ParsedArgs,
140//! ) -> dynamic_cli::Result<()> {
141//! let ctx = dynamic_cli::context::downcast_mut::<AppContext>(context).unwrap();
142//! let item = args.get_scalar("item").unwrap();
143//! ctx.data.push(item.to_string());
144//! println!("Added: {}", item);
145//! Ok(())
146//! }
147//! }
148//!
149//! fn main() -> dynamic_cli::Result<()> {
150//! // Load configuration
151//! let config = load_config("commands.yaml")?;
152//!
153//! // Build registry
154//! let mut registry = CommandRegistry::new();
155//! registry.register_sync(
156//! config.commands[0].clone(),
157//! Box::new(AddCommand),
158//! )?;
159//!
160//! // Create and run CLI
161//! let context = Box::new(AppContext::default());
162//! let cli = CliInterface::new(registry, context);
163//! cli.run(std::env::args().skip(1).collect())
164//! }
165//! ```
166//!
167//! ## Complete REPL Application
168//!
169//! ```no_run
170//! use dynamic_cli::prelude::*;
171//! use dynamic_cli::config::loader::load_config;
172//! use dynamic_cli::interface::ReplInterface;
173//!
174//! // Same context and handler as above
175//! # #[derive(Default)]
176//! # struct AppContext { data: Vec<String> }
177//! # impl ExecutionContext for AppContext {
178//! # fn as_any(&self) -> &dyn std::any::Any { self }
179//! # fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
180//! # }
181//! # struct AddCommand;
182//! # impl CommandHandler for AddCommand {
183//! # fn execute(&self, context: &mut dyn ExecutionContext, args: &ParsedArgs) -> dynamic_cli::Result<()> {
184//! # let ctx = dynamic_cli::context::downcast_mut::<AppContext>(context).unwrap();
185//! # let item = args.get_scalar("item").unwrap();
186//! # ctx.data.push(item.to_string());
187//! # println!("Added: {}", item);
188//! # Ok(())
189//! # }
190//! # }
191//!
192//! fn main() -> dynamic_cli::Result<()> {
193//! // Load configuration
194//! let config = load_config("commands.yaml")?;
195//!
196//! // Build registry
197//! let mut registry = CommandRegistry::new();
198//! registry.register_sync(
199//! config.commands[0].clone(),
200//! Box::new(AddCommand),
201//! )?;
202//!
203//! // Create and run REPL
204//! let context = Box::new(AppContext::default());
205//! let repl = ReplInterface::new(registry, context, "myapp".to_string(), None, None)?;
206//! repl.run() // Interactive loop
207//! }
208//! ```
209//!
210//! # Module Structure
211//!
212//! - [`cli`]: CLI interface implementation
213//! - [`repl`]: REPL interface implementation
214//!
215//! [`CliParser`]: crate::parser::CliParser
216//! [`ReplParser`]: crate::parser::ReplParser
217
218pub mod cli;
219pub mod repl;
220
221// Re-export main types for convenience
222pub use cli::{CliInterface, ScriptErrorPolicy, ScriptOutcome};
223pub use repl::ReplInterface;
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::config::schema::CommandDefinition;
229 use crate::prelude::*;
230
231 // Test context
232 struct TestContext;
233
234 impl ExecutionContext for TestContext {
235 fn as_any(&self) -> &dyn std::any::Any {
236 self
237 }
238
239 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
240 self
241 }
242 }
243
244 // Test handler
245 struct TestHandler;
246
247 impl CommandHandler for TestHandler {
248 fn execute(
249 &self,
250 _context: &mut dyn ExecutionContext,
251 _args: &ParsedArgs,
252 ) -> crate::Result<()> {
253 Ok(())
254 }
255 }
256
257 #[test]
258 fn test_module_imports() {
259 // Verify that types are re-exported
260 let _: Option<CliInterface> = None;
261 let _: Option<ReplInterface> = None;
262 }
263
264 #[test]
265 fn test_cli_interface_accessible() {
266 let mut registry = CommandRegistry::new();
267
268 let cmd_def = CommandDefinition {
269 name: "test".to_string(),
270 aliases: vec![],
271 description: "Test".to_string(),
272 required: false,
273 arguments: vec![],
274 options: vec![],
275 implementation: "test".to_string(),
276 };
277
278 registry
279 .register_sync(cmd_def, Box::new(TestHandler))
280 .unwrap();
281
282 let context = Box::new(TestContext);
283 let _cli = CliInterface::new(registry, context);
284 }
285
286 #[test]
287 fn test_repl_interface_accessible() {
288 let mut registry = CommandRegistry::new();
289
290 let cmd_def = CommandDefinition {
291 name: "test".to_string(),
292 aliases: vec![],
293 description: "Test".to_string(),
294 required: false,
295 arguments: vec![],
296 options: vec![],
297 implementation: "test".to_string(),
298 };
299
300 registry
301 .register_sync(cmd_def, Box::new(TestHandler))
302 .unwrap();
303
304 let context = Box::new(TestContext);
305 let _repl = ReplInterface::new(registry, context, "test".to_string(), None, None);
306 }
307}