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;
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 #[derive(Default)]
233 struct TestContext;
234
235 impl ExecutionContext for TestContext {
236 fn as_any(&self) -> &dyn std::any::Any {
237 self
238 }
239
240 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
241 self
242 }
243 }
244
245 // Test handler
246 struct TestHandler;
247
248 impl CommandHandler for TestHandler {
249 fn execute(
250 &self,
251 _context: &mut dyn ExecutionContext,
252 _args: &ParsedArgs,
253 ) -> crate::Result<()> {
254 Ok(())
255 }
256 }
257
258 #[test]
259 fn test_module_imports() {
260 // Verify that types are re-exported
261 let _: Option<CliInterface> = None;
262 let _: Option<ReplInterface> = None;
263 }
264
265 #[test]
266 fn test_cli_interface_accessible() {
267 let mut registry = CommandRegistry::new();
268
269 let cmd_def = CommandDefinition {
270 name: "test".to_string(),
271 aliases: vec![],
272 description: "Test".to_string(),
273 required: false,
274 arguments: vec![],
275 options: vec![],
276 implementation: "test".to_string(),
277 };
278
279 registry
280 .register_sync(cmd_def, Box::new(TestHandler))
281 .unwrap();
282
283 let context = Box::new(TestContext::default());
284 let _cli = CliInterface::new(registry, context);
285 }
286
287 #[test]
288 fn test_repl_interface_accessible() {
289 let mut registry = CommandRegistry::new();
290
291 let cmd_def = CommandDefinition {
292 name: "test".to_string(),
293 aliases: vec![],
294 description: "Test".to_string(),
295 required: false,
296 arguments: vec![],
297 options: vec![],
298 implementation: "test".to_string(),
299 };
300
301 registry
302 .register_sync(cmd_def, Box::new(TestHandler))
303 .unwrap();
304
305 let context = Box::new(TestContext::default());
306 let _repl = ReplInterface::new(registry, context, "test".to_string(), None, None);
307 }
308}