Expand description
User interface module
This module provides two main interfaces for interacting with the CLI framework:
CliInterface: One-shot command execution from command-line argumentsReplInterface: Interactive REPL (Read-Eval-Print Loop) with history
§Overview
The interface module is the user-facing layer of the framework. It handles:
- Parsing user input (CLI args or REPL lines)
- Executing commands through the registry
- Displaying results and errors
- Managing command history (REPL only)
§Choosing an Interface
§CLI Interface
Use CliInterface when:
- Running single commands from scripts
- Building traditional CLI tools
- No interaction is needed
- Each invocation is independent
use dynamic_cli::interface::CliInterface;
use dynamic_cli::prelude::*;
let registry = CommandRegistry::new();
let context = Box::new(MyContext::default());
let cli = CliInterface::new(registry, context);
cli.run(std::env::args().skip(1).collect())?;§REPL Interface
Use ReplInterface when:
- Building interactive tools
- Users need to run multiple commands
- Context/state is preserved between commands
- Command history and line editing are desired
use dynamic_cli::interface::ReplInterface;
use dynamic_cli::prelude::*;
let registry = CommandRegistry::new();
let context = Box::new(MyContext::default());
let repl = ReplInterface::new(registry, context, "myapp".to_string(), None, None)?;
repl.run()?; // Enters interactive loop§Architecture
Both interfaces follow the same flow:
User Input → Parser → Validator → Executor → Handler
↓
ExecutionContextKey differences:
| Aspect | CLI | REPL |
|---|---|---|
| Input | Command-line args | Interactive lines |
| Parser | CliParser | ReplParser |
| History | None | Written to disk by :quit and Ctrl-D |
| Errors | Exit process | Display and continue |
| Lifecycle | One command, exit | Loop until :quit, :exit or Ctrl-D |
§Error Handling
§CLI Interface
Errors cause the process to exit with specific codes:
0: Success1: Execution error2: Parse/validation error3: Other errors
§REPL Interface
Errors are displayed but the REPL continues:
- Parse errors → show suggestions, continue
- Validation errors → explain issue, continue
- Execution errors → display error, continue
- Directive errors → list the available directives (unknown directive) or show the expected usage (wrong arguments), continue
- Input failure (the terminal cannot be read) → reported, the REPL exits without writing the history
§REPL Directives
In the REPL, a line starting with : is a framework directive; any
other line is an application command. The two never compete for a name:
:quit is always the directive, quit is always looked up among the
application’s commands.
| Directive | Aliases | Action |
|---|---|---|
:help [command] | :h, :? | Application help, or the help of command |
:load <path> | Run every line of a script file | |
:quit | :q | Write the session history, then leave |
:exit | Leave without writing the session history |
These are the default names. An application can rename a directive,
replace its aliases or its description through the directives: section
of its configuration, for instance to translate them; it can neither add
nor disable one. See crate::config::directive.
Leaving the REPL:
:quitand end of input (Ctrl-D) write the session history, then leave.:exitleaves without writing it. Any other termination (a handler callingstd::process::exit, a panic, the process being killed) also leaves the history file as it was when the session started.exitorquittyped without:is an ordinary command name. When the application has no command of that name, the error suggests the matching directive (:quit).
Inside a :load script, :quit and :exit are reported and skipped;
the other lines run.
:help lists the directives after the application help, under their
effective names (see
HelpFormatter::format_directives).
Tab completion after a leading : offers the directive names and
aliases. --help and -h work in the REPL as in CLI mode.
§Examples
§Complete CLI Application
use dynamic_cli::prelude::*;
use dynamic_cli::config::loader::load_config;
use dynamic_cli::interface::CliInterface;
// Define context
#[derive(Default)]
struct AppContext {
data: Vec<String>,
}
impl ExecutionContext for AppContext {
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
// Define handler
struct AddCommand;
impl CommandHandler for AddCommand {
fn execute(
&self,
context: &mut dyn ExecutionContext,
args: &ParsedArgs,
) -> dynamic_cli::Result<()> {
let ctx = dynamic_cli::context::downcast_mut::<AppContext>(context).unwrap();
let item = args.get_scalar("item").unwrap();
ctx.data.push(item.to_string());
println!("Added: {}", item);
Ok(())
}
}
fn main() -> dynamic_cli::Result<()> {
// Load configuration
let config = load_config("commands.yaml")?;
// Build registry
let mut registry = CommandRegistry::new();
registry.register_sync(
config.commands[0].clone(),
Box::new(AddCommand),
)?;
// Create and run CLI
let context = Box::new(AppContext::default());
let cli = CliInterface::new(registry, context);
cli.run(std::env::args().skip(1).collect())
}§Complete REPL Application
use dynamic_cli::prelude::*;
use dynamic_cli::config::loader::load_config;
use dynamic_cli::interface::ReplInterface;
// Same context and handler as above
fn main() -> dynamic_cli::Result<()> {
// Load configuration
let config = load_config("commands.yaml")?;
// Build registry
let mut registry = CommandRegistry::new();
registry.register_sync(
config.commands[0].clone(),
Box::new(AddCommand),
)?;
// Create and run REPL
let context = Box::new(AppContext::default());
let repl = ReplInterface::new(registry, context, "myapp".to_string(), None, None)?;
repl.run() // Interactive loop
}§Module Structure
Re-exports§
pub use cli::CliInterface;pub use cli::ScriptErrorPolicy;pub use cli::ScriptOutcome;pub use repl::ReplInterface;