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}