# ClapCmd A library to quickly build full-featured REPLs supported by CLAP and readline (provided via rustyline) ## Features - Full readline support that exposes all customization for rustyline - emacs-style keyboard shortcuts by default (customizable via rustyline) - command history (in memory buffer) - Full integration with clap builders allowing for full-featured commands - Tab completion for: - commands and (TODO) command aliases - arguments - subcommands - values supplied via `value_parsers` (i.e. a list of valid values) - value hints (e.g. `ValueHint::FilePath`) - TODO: callback and/or demo for how to query `value_parsers` at runtime - Callback style approach with provided state - Customizable prompts that can be updated at anytime during execution - Support for writing to stdout outside of the command loop without mangling the input line via `get_async_writer()` - Create loadable and unloadable command groups - Multiline input support via the '\\' character at end-of-line - Combine multiple commands in one line via: - semicolon (`;`) for unconditional evaluation - double ampersand (`&&`) for chaining successful evaluations - double pipe (`||`) for error handling evaluations - Automated testing via the `test-runner` feature ## Basic Example A minimal example showing a basic REPL is as follows: ```rust use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command}; fn do_ping(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult { cmd.output("pong"); Ok(()) } fn main() { let mut cmd = ClapCmd::default(); cmd.add_command( do_ping, Command::new("ping").about("do a ping") ); cmd.run_loop(); } ``` ## With State To pass state or persistent information to callbacks, provide a `State` class like so. The `State` class must implement `Clone` trait, and can be accessed via the `get_state()` and `set_state()` methods on the `ClapCmd` reference passed into the callback. ```rust use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command}; #[derive(Clone)] struct State { counter: u32, } fn do_count(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult { let state = cmd.get_state().ok_or("state missing")?; let new_count = state.counter + 1; cmd.info(format!("the count is now: {}", new_count)); cmd.set_state(State { counter: new_count }); Ok(()) } fn main() { let mut cmd = ClapCmd::with_state(State { counter: 0 }); cmd.add_command(do_count, Command::new("count").about("increment a counter")); cmd.run_loop(); } ``` ## Using Groups Groups can be used to logically separate sets of commands in the built-in `help` menu. They can also be used to quickly activate and deactivate commands via the `add_group` and `remove_group` methods ```rust use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command, HandlerGroup}; use once_cell::sync::Lazy; static LOADED_GROUP: Lazy = Lazy::new(|| { ClapCmd::group("Fruit") .description("Commands to do cool fruit things") .command( do_apple, Command::new("apple").about("do the cool apple thing"), ) .command( do_banana, Command::new("banana").about("do the cool banana thing"), ) .command( do_unload, Command::new("unload").about("unload the cool fruit group"), ) }); static UNLOADED_GROUP: Lazy = Lazy::new(|| { ClapCmd::unnamed_group().command( do_load, Command::new("load").about("load the cool fruit group"), ) }); fn do_load(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult { cmd.add_group(&LOADED_GROUP); cmd.remove_group(&UNLOADED_GROUP); cmd.info("loaded"); Ok(()) } fn do_unload(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult { cmd.add_group(&UNLOADED_GROUP); cmd.remove_group(&LOADED_GROUP); cmd.info("unloaded"); Ok(()) } fn do_apple(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult { cmd.output("apple"); Ok(()) } fn do_banana(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult { cmd.output("banana"); Ok(()) } fn main() { let mut cmd = ClapCmd::default(); cmd.add_group(&UNLOADED_GROUP); cmd.run_loop(); } ``` ## E2E Testing By enabling the `test-runner` feature and using the built-in `output`, `success`, `info`, `warn`, and `error` functions, it is easy to automate e2e tests of your CLI. See the [`tests/`](https://gitlab.com/clapcmd/clapcmd/-/tree/main/tests?ref_type=heads) folder for more examples. ```rust use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command}; fn do_hello(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult { cmd.output("hello"); Ok(()) } let mut cmd = ClapCmd::default(); cmd.add_command( do_hello, Command::new("hello").about("simple hello world") ); let _ = cmd.one_cmd("goodbye"); #[cfg(feature = "test-runner")] assert!( cmd.error.contains("unknown command"), "did not detect invalid command", ); let _ = cmd.one_cmd("hello"); #[cfg(feature = "test-runner")] assert!( cmd.output.contains("hello"), "did not run hello world command correctly", ); ``` ## Other Examples Refer to the [`examples/`](https://gitlab.com/clapcmd/clapcmd/-/tree/main/examples?ref_type=heads) folder for more demonstrations of advanced use cases ## MSRV This library is tested with Rust 1.65 along with the latest version of Rust ## Related Projects - reedline-repl-rs [https://github.com/arturh85/reedline-repl-rs](https://github.com/arturh85/reedline-repl-rs)