dynamic_cli/lib.rs
1//! # dynamic-cli
2//!
3//! A framework for creating configurable CLI and REPL applications via YAML/JSON.
4//!
5//! ## Overview
6//!
7//! **dynamic-cli** allows you to define your application's command-line interface
8//! in a configuration file rather than coding it manually. The framework
9//! automatically generates:
10//! - Argument parsing
11//! - Input validation
12//! - Contextual help
13//! - Interactive mode (REPL)
14//! - Error handling with suggestions
15//!
16//! ## Quick Start
17//!
18//! ```no_run
19//! use dynamic_cli::prelude::*;
20//!
21//! // 1. Define the execution context
22//! #[derive(Default)]
23//! struct MyContext;
24//!
25//! impl ExecutionContext for MyContext {
26//! fn as_any(&self) -> &dyn std::any::Any { self }
27//! fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
28//! }
29//!
30//! // 2. Implement a command handler
31//! struct HelloCommand;
32//!
33//! impl CommandHandler for HelloCommand {
34//! fn execute(
35//! &self,
36//! _context: &mut dyn ExecutionContext,
37//! args: &ParsedArgs,
38//! ) -> dynamic_cli::Result<()> {
39//! let name = args.get_scalar("name").unwrap_or("World");
40//! println!("Hello, {}!", name);
41//! Ok(())
42//! }
43//! }
44//!
45//! // 3. Load configuration and register commands
46//! # fn main() -> dynamic_cli::Result<()> {
47//! use dynamic_cli::config::loader::load_config;
48//!
49//! let config = load_config("commands.yaml")?;
50//! let mut registry = CommandRegistry::new();
51//! registry.register_sync(config.commands[0].clone(), Box::new(HelloCommand))?;
52//!
53//! // 4. Parse and execute
54//! let parser = ReplParser::new(®istry);
55//! let parsed = parser.parse_line("hello World")?;
56//!
57//! let mut context = MyContext::default();
58//! let handler = registry.get_handler_sync(&parsed.command_name).unwrap();
59//! let args = ParsedArgs::from_scalars(parsed.arguments);
60//! handler.execute(&mut context, &args)?;
61//! # Ok(())
62//! # }
63//! ```
64//!
65//! ## Architecture
66//!
67//! The framework is organized into modules:
68//!
69//! - [`builder`]: [`CliBuilder`] and [`CliApp`], the application entry point
70//! - [`error`]: Error types and handling
71//! - [`config`]: Configuration file loading and validation, REPL directives
72//! - [`context`]: Execution context trait
73//! - [`executor`]: Command execution
74//! - [`registry`]: Command and handler registry
75//! - [`parser`]: CLI and REPL argument parsing
76//! - [`validator`]: Argument validation
77//! - [`interface`]: CLI one-shot and REPL front ends
78//! - [`help`]: Help formatting
79//! - [`plugin`]: Plugin trait, builtin plugins and WASM loader
80//! - [`utils`]: Shared helpers
81//!
82//! ## Design decisions
83//!
84//! Design decisions are tracked as GitHub issues labelled `type: decision`:
85//! <https://github.com/biface/dcli/issues?q=label%3A%22type%3A+decision%22>.
86//!
87//! ## Module Status
88//!
89//! - ✅ Complete: error, config, context, executor, registry, parser, validator, interface, builder
90//! - 📋 Planned: utils, examples
91//!
92//! ## Examples
93//!
94//! See the documentation for each module for detailed examples.
95
96// ============================================================================
97// PUBLIC MODULES (Complete and ready to use)
98// ============================================================================
99
100pub mod builder;
101pub mod config;
102pub mod context;
103pub mod error;
104pub mod executor;
105pub mod help;
106pub mod interface;
107pub mod parser;
108pub mod plugin;
109pub mod registry;
110pub mod utils;
111pub mod validator;
112// ============================================================================
113// PUBLIC RE-EXPORTS (For convenience)
114// ============================================================================
115
116// Core traits
117pub use context::{downcast_mut, downcast_ref, ExecutionContext};
118pub use executor::{AsyncCommandHandler, CommandHandler};
119
120// Error handling
121pub use error::{DynamicCliError, Result};
122
123// Configuration types
124pub use config::schema::{
125 ArgumentDefinition, ArgumentType, CommandDefinition, CommandsConfig, Metadata,
126 OptionDefinition, ValidationRule,
127};
128pub use config::{effective_directives, DirectiveDefinition, ReplDirective};
129
130// Registry
131pub use registry::CommandRegistry;
132
133// Parser types
134pub use parser::{CliParser, ParsedArgs, ParsedCommand, ReplParser};
135
136// Validator functions
137pub use validator::{validate_file_exists, validate_file_extension, validate_range};
138
139// Interface types
140pub use interface::{CliInterface, ReplInterface, ScriptErrorPolicy, ScriptOutcome};
141
142// Builder types
143pub use builder::{CliApp, CliBuilder};
144
145// Helper system
146pub use help::{DefaultHelpFormatter, HelpFormatter};
147
148// Plugin system
149#[cfg(feature = "wasm-plugins")]
150pub use plugin::wasm::{WasmPlugin, WasmSerializationFormat};
151#[cfg(feature = "config-plugin")]
152pub use plugin::ConfigPlugin;
153#[cfg(feature = "env-plugin")]
154pub use plugin::EnvPlugin;
155#[cfg(feature = "sysinfo-plugin")]
156pub use plugin::SysInfoPlugin;
157pub use plugin::{ExitPlugin, HelpPlugin, Plugin, SystemPlugin, VersionPlugin};
158
159// Utility functions
160pub use utils::{
161 detect_type, format_bytes, format_duration, get_extension, has_extension, is_blank, normalize,
162 normalize_path, parse_bool, parse_float, parse_int, truncate,
163};
164
165// ============================================================================
166// PRELUDE MODULE (Quick imports)
167// ============================================================================
168
169/// Prelude module for quickly importing essential types
170///
171/// This module re-exports the most commonly used types and traits,
172/// allowing you to import everything with a single `use` statement.
173///
174/// # Example
175///
176/// ```
177/// use dynamic_cli::prelude::*;
178///
179/// // Now you have access to:
180/// // - ExecutionContext, downcast_ref, downcast_mut
181/// // - CommandHandler, AsyncCommandHandler
182/// // - DynamicCliError, Result
183/// // - CommandRegistry
184/// // - ParsedCommand, CliParser, ReplParser
185/// // - validate_file_exists, validate_file_extension, validate_range
186/// // - Common config types (ArgumentType, CommandsConfig)
187/// // - CliBuilder, CliApp
188/// // - Utility functions (parse_int, parse_bool, is_blank, etc.)
189/// ```
190pub mod prelude {
191 // Context management
192 pub use crate::context::{downcast_mut, downcast_ref, ExecutionContext};
193
194 // Command handling
195 pub use crate::executor::{AsyncCommandHandler, CommandHandler};
196
197 // Error handling
198 pub use crate::error::{DynamicCliError, Result};
199
200 // Configuration
201 pub use crate::config::schema::{ArgumentType, CommandsConfig};
202
203 // Registry
204 pub use crate::registry::CommandRegistry;
205
206 // Parsing
207 pub use crate::parser::{CliParser, ParsedArgs, ParsedCommand, ReplParser};
208
209 // Validation
210 pub use crate::validator::{validate_file_exists, validate_file_extension, validate_range};
211
212 // Interface
213 pub use crate::interface::{CliInterface, ReplInterface, ScriptErrorPolicy, ScriptOutcome};
214
215 // Builder
216 pub use crate::builder::{CliApp, CliBuilder};
217
218 // Help system — re-exported so framework users need only `use dynamic_cli::prelude::*`
219 pub use crate::help::{DefaultHelpFormatter, HelpFormatter};
220
221 // Plugin system
222 #[cfg(feature = "wasm-plugins")]
223 pub use crate::plugin::wasm::{WasmPlugin, WasmSerializationFormat};
224 #[cfg(feature = "config-plugin")]
225 pub use crate::plugin::ConfigPlugin;
226 #[cfg(feature = "env-plugin")]
227 pub use crate::plugin::EnvPlugin;
228 #[cfg(feature = "sysinfo-plugin")]
229 pub use crate::plugin::SysInfoPlugin;
230 pub use crate::plugin::{ExitPlugin, HelpPlugin, Plugin, SystemPlugin, VersionPlugin};
231
232 // Utilities (most commonly used)
233 pub use crate::utils::{detect_type, is_blank, normalize, parse_bool, parse_float, parse_int};
234}
235
236// ============================================================================
237// INTEGRATION TESTS
238// ============================================================================
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 /// Verify that prelude imports work correctly
245 #[test]
246 fn test_prelude_imports() {
247 use crate::prelude::*;
248
249 // If this compiles, prelude imports are working
250 let _: Option<&dyn ExecutionContext> = None;
251 let _: Option<&dyn CommandHandler> = None;
252 let _: Option<&dyn AsyncCommandHandler> = None;
253 }
254
255 /// Verify that individual module imports work
256 #[test]
257 fn test_module_imports() {
258 use crate::config::schema::CommandsConfig;
259 use crate::parser::ParsedCommand;
260 use crate::registry::CommandRegistry;
261
262 // If this compiles, module structure is correct
263 let _config = CommandsConfig::minimal();
264 let _registry = CommandRegistry::new();
265 let _parsed = ParsedCommand {
266 command_name: "test".to_string(),
267 arguments: std::collections::HashMap::new(),
268 };
269 }
270
271 /// Verify that re-exports work
272 #[test]
273 fn test_reexports() {
274 // These should be accessible from the crate root
275 let _: Option<&dyn ExecutionContext> = None;
276 let _: Option<&dyn CommandHandler> = None;
277 let _: Option<&dyn AsyncCommandHandler> = None;
278 let _registry = CommandRegistry::new();
279
280 // If this compiles, re-exports are working
281 }
282
283 /// Verify that help types are accessible from the prelude
284 #[test]
285 fn test_help_prelude_imports() {
286 use crate::config::schema::{CommandsConfig, Metadata};
287 use crate::prelude::*;
288
289 let config = CommandsConfig {
290 metadata: Metadata {
291 version: "1.0.0".to_string(),
292 prompt: "test".to_string(),
293 prompt_suffix: " > ".to_string(),
294 },
295 commands: vec![],
296 global_options: vec![],
297 directives: vec![],
298 };
299
300 // DefaultHelpFormatter accessible from prelude
301 let f = DefaultHelpFormatter::new();
302 let _ = f.format_app(&config);
303
304 // Trait object usable (object-safe by design)
305 let _: Box<dyn HelpFormatter> = Box::new(DefaultHelpFormatter::new());
306 }
307}