Expand description
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
- semicolon (
- Automated testing via the
test-runner
feature
Basic Example
A minimal example showing a basic REPL is as follows:
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.
use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command};
#[derive(Clone)]
struct State {
counter: u32,
}
fn do_count(cmd: &mut ClapCmd<State>, _: 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
use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command, HandlerGroup};
use once_cell::sync::Lazy;
static LOADED_GROUP: Lazy<HandlerGroup> = 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<HandlerGroup> = 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/
folder for more examples.
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/
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
Re-exports
Modules
Structs
- The abstract representation of a command line argument. Used to set all the options and relationships that define a valid argument for the program.
- Container for parse results.
- An interactive CLI interface, holding state and responsible for acquiring user input and assigning tasks to callback functions.
- The ClapCmdBuilder is mostly a thin wrapper around the rustyline builder that allows you to specify how to interact with the readline interface. The main difference is that the default options for the ClapCmdBuilder includes
auto_add_history
- Build a command-line interface.
Enums
- Provide shell with hint on how to complete an argument.