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())?;
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//! use std::collections::HashMap;
121//!
122//! // Define context
123//! #[derive(Default)]
124//! struct AppContext {
125//! data: Vec<String>,
126//! }
127//!
128//! impl ExecutionContext for AppContext {
129//! fn as_any(&self) -> &dyn std::any::Any { self }
130//! fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
131//! }
132//!
133//! // Define handler
134//! struct AddCommand;
135//!
136//! impl CommandHandler for AddCommand {
137//! fn execute(
138//! &self,
139//! context: &mut dyn ExecutionContext,
140//! args: &HashMap<String, String>,
141//! ) -> dynamic_cli::Result<()> {
142//! let ctx = dynamic_cli::context::downcast_mut::<AppContext>(context).unwrap();
143//! let item = args.get("item").unwrap();
144//! ctx.data.push(item.clone());
145//! println!("Added: {}", item);
146//! Ok(())
147//! }
148//! }
149//!
150//! fn main() -> dynamic_cli::Result<()> {
151//! // Load configuration
152//! let config = load_config("commands.yaml")?;
153//!
154//! // Build registry
155//! let mut registry = CommandRegistry::new();
156//! registry.register(
157//! config.commands[0].clone(),
158//! Box::new(AddCommand),
159//! )?;
160//!
161//! // Create and run CLI
162//! let context = Box::new(AppContext::default());
163//! let cli = CliInterface::new(registry, context);
164//! cli.run(std::env::args().skip(1).collect())
165//! }
166//! ```
167//!
168//! ## Complete REPL Application
169//!
170//! ```no_run
171//! use dynamic_cli::prelude::*;
172//! use dynamic_cli::config::loader::load_config;
173//! use dynamic_cli::interface::ReplInterface;
174//! use std::collections::HashMap;
175//!
176//! // Same context and handler as above
177//! # #[derive(Default)]
178//! # struct AppContext { data: Vec<String> }
179//! # impl ExecutionContext for AppContext {
180//! # fn as_any(&self) -> &dyn std::any::Any { self }
181//! # fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
182//! # }
183//! # struct AddCommand;
184//! # impl CommandHandler for AddCommand {
185//! # fn execute(&self, context: &mut dyn ExecutionContext, args: &HashMap<String, String>) -> dynamic_cli::Result<()> {
186//! # let ctx = dynamic_cli::context::downcast_mut::<AppContext>(context).unwrap();
187//! # let item = args.get("item").unwrap();
188//! # ctx.data.push(item.clone());
189//! # println!("Added: {}", item);
190//! # Ok(())
191//! # }
192//! # }
193//!
194//! fn main() -> dynamic_cli::Result<()> {
195//! // Load configuration
196//! let config = load_config("commands.yaml")?;
197//!
198//! // Build registry
199//! let mut registry = CommandRegistry::new();
200//! registry.register(
201//! config.commands[0].clone(),
202//! Box::new(AddCommand),
203//! )?;
204//!
205//! // Create and run REPL
206//! let context = Box::new(AppContext::default());
207//! let repl = ReplInterface::new(registry, context, "myapp".to_string())?;
208//! repl.run() // Interactive loop
209//! }
210//! ```
211//!
212//! # Module Structure
213//!
214//! - [`cli`]: CLI interface implementation
215//! - [`repl`]: REPL interface implementation
216//!
217//! [`CliParser`]: crate::parser::CliParser
218//! [`ReplParser`]: crate::parser::ReplParser
219
220pub mod cli;
221pub mod repl;
222
223// Re-export main types for convenience
224pub use cli::CliInterface;
225pub use repl::ReplInterface;
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::config::schema::CommandDefinition;
231 use crate::prelude::*;
232 use std::collections::HashMap;
233
234 // Test context
235 #[derive(Default)]
236 struct TestContext;
237
238 impl ExecutionContext for TestContext {
239 fn as_any(&self) -> &dyn std::any::Any {
240 self
241 }
242
243 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
244 self
245 }
246 }
247
248 // Test handler
249 struct TestHandler;
250
251 impl CommandHandler for TestHandler {
252 fn execute(
253 &self,
254 _context: &mut dyn ExecutionContext,
255 _args: &HashMap<String, String>,
256 ) -> crate::Result<()> {
257 Ok(())
258 }
259 }
260
261 #[test]
262 fn test_module_imports() {
263 // Verify that types are re-exported
264 let _: Option<CliInterface> = None;
265 let _: Option<ReplInterface> = None;
266 }
267
268 #[test]
269 fn test_cli_interface_accessible() {
270 let mut registry = CommandRegistry::new();
271
272 let cmd_def = CommandDefinition {
273 name: "test".to_string(),
274 aliases: vec![],
275 description: "Test".to_string(),
276 required: false,
277 arguments: vec![],
278 options: vec![],
279 implementation: "test".to_string(),
280 };
281
282 registry.register(cmd_def, Box::new(TestHandler)).unwrap();
283
284 let context = Box::new(TestContext::default());
285 let _cli = CliInterface::new(registry, context);
286 }
287
288 #[test]
289 fn test_repl_interface_accessible() {
290 let mut registry = CommandRegistry::new();
291
292 let cmd_def = CommandDefinition {
293 name: "test".to_string(),
294 aliases: vec![],
295 description: "Test".to_string(),
296 required: false,
297 arguments: vec![],
298 options: vec![],
299 implementation: "test".to_string(),
300 };
301
302 registry.register(cmd_def, Box::new(TestHandler)).unwrap();
303
304 let context = Box::new(TestContext::default());
305 let _repl = ReplInterface::new(registry, context, "test".to_string());
306 }
307}