Skip to main content

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}