Crate reedline_repl_rs
source ·Expand description
reedline-repl-rs - REPL library for Rust
§Example
//! Minimal example
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
use reedline_repl_rs::{Repl, Result};
/// Write "Hello" with given name
fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
Ok(Some(format!(
"Hello, {}",
args.get_one::<String>("who").unwrap()
)))
}
fn main() -> Result<()> {
let mut repl = Repl::new(())
.with_name("MyApp")
.with_version("v0.1.0")
.with_description("My very cool app")
.with_banner("Welcome to MyApp")
.with_command(
Command::new("hello")
.arg(Arg::new("who").required(true))
.about("Greetings!"),
hello,
);
repl.run()
}
reedline-repl-rs uses the builder pattern extensively. What these lines are doing is:
- creating a repl with an empty Context (see below)
- with a name of “MyApp”, the given version, and the given description
- and adding a “hello” command which calls out to the
hello
callback function defined above - the
hello
command has a single parameter, “who”, which is required, and has the given help message
The hello
function takes a reference to ArgMatches,
and an (unused) Context
, which is used to hold state if you
need to - the initial context is passed in to the call to
Repl::new, in our case, ()
.
Because we’re not using a Context, we need to include a generic type in our hello
function,
because there’s no way to pass an argument of type ()
otherwise.
All command function callbacks return a Result<Option<String>>
. This has the following
effect:
- If the return is
Ok(Some(String))
, it prints the string to stdout - If the return is
Ok(None)
, it prints nothing - If the return is an error, it prints the error message to stderr
§Context
The Context
type is used to keep state between REPL calls. Here’s an example:
//! Example using Repl with Context
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
use reedline_repl_rs::{Repl, Result};
use std::collections::VecDeque;
#[derive(Default)]
struct Context {
list: VecDeque<String>,
}
/// Append name to list
fn append(args: ArgMatches, context: &mut Context) -> Result<Option<String>> {
let name: String = args.get_one::<String>("name").unwrap().to_string();
context.list.push_back(name);
let list: Vec<String> = context.list.clone().into();
Ok(Some(list.join(", ")))
}
/// Prepend name to list
fn prepend(args: ArgMatches, context: &mut Context) -> Result<Option<String>> {
let name: String = args.get_one::<String>("name").unwrap().to_string();
context.list.push_front(name);
let list: Vec<String> = context.list.clone().into();
Ok(Some(list.join(", ")))
}
fn main() -> Result<()> {
let mut repl = Repl::new(Context::default())
.with_name("MyList")
.with_version("v0.1.0")
.with_description("My very cool List")
.with_command(
Command::new("append")
.arg(Arg::new("name").required(true))
.about("Append name to end of list"),
append,
)
.with_command(
Command::new("prepend")
.arg(Arg::new("name").required(true))
.about("Prepend name to front of list"),
prepend,
)
.with_on_after_command(|context| Ok(Some(format!("MyList [{}]", context.list.len()))));
repl.run()
}
A few things to note:
- you pass in the initial value for your Context struct to the call to Repl::new()
- the context is passed to your command callback functions as a mutable reference
- the prompt can be changed after each executed commmand using with_on_after_command as shown
§Async Support
The async
feature allows you to write async REPL code:
//! Example using Repl with a custom error type.
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
use reedline_repl_rs::{Repl, Result};
/// Write "Hello" with given name
async fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
Ok(Some(format!(
"Hello, {}",
args.get_one::<String>("who").unwrap()
)))
}
/// Called after successful command execution, updates prompt with returned Option
async fn update_prompt<T>(_context: &mut T) -> Result<Option<String>> {
Ok(Some("updated".to_string()))
}
#[tokio::main]
async fn main() -> Result<()> {
let mut repl = Repl::new(())
.with_name("MyApp")
.with_version("v0.1.0")
.with_command_async(
Command::new("hello")
.arg(Arg::new("who").required(true))
.about("Greetings!"),
|args, context| Box::pin(hello(args, context)),
)
.with_on_after_command_async(|context| Box::pin(update_prompt(context)));
repl.run_async().await
}
A few things to note:
- The ugly Pin::Box workaround is required because of unstable rust async Fn’s
§Keybindings
Per default Emacs-style keybindings are used
//! Example with custom Keybinding
use reedline::{EditCommand, ReedlineEvent};
use reedline::{KeyCode, KeyModifiers};
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
use reedline_repl_rs::{Repl, Result};
/// Write "Hello" with given name
fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
Ok(Some(format!(
"Hello, {}",
args.get_one::<String>("who").unwrap()
)))
}
fn main() -> Result<()> {
let mut repl = Repl::new(())
.with_name("MyApp")
.with_version("v0.1.0")
.with_description("My very cool app")
.with_banner("Welcome to MyApp")
.with_command(
Command::new("hello")
.arg(Arg::new("who").required(true))
.about("Greetings!"),
hello,
)
// greet friend with CTRG+g
.with_keybinding(
KeyModifiers::CONTROL,
KeyCode::Char('g'),
ReedlineEvent::ExecuteHostCommand("hello Friend".to_string()),
)
// show help with CTRL+h
.with_keybinding(
KeyModifiers::CONTROL,
KeyCode::Char('h'),
ReedlineEvent::ExecuteHostCommand("help".to_string()),
)
// uppercase current word with CTRL+u
.with_keybinding(
KeyModifiers::CONTROL,
KeyCode::Char('u'),
ReedlineEvent::Edit(vec![EditCommand::UppercaseWord]),
)
// uppercase current word with CTRL+l
.with_keybinding(
KeyModifiers::CONTROL,
KeyCode::Char('l'),
ReedlineEvent::Edit(vec![EditCommand::LowercaseWord]),
);
println!("Keybindings:");
let keybindings = repl.get_keybindings();
for search_modifier in [
KeyModifiers::NONE,
KeyModifiers::CONTROL,
KeyModifiers::SHIFT,
KeyModifiers::ALT,
] {
for ((modifier, key_code), reedline_event) in &keybindings {
if *modifier == search_modifier {
println!("{:?} + {:?} => {:?}", modifier, key_code, reedline_event);
}
}
}
repl.run()
}
A few things to note:
- The ugly Pin::Box workaround is required because of unstable rust async Fn’s
§Help
reedline-repl-rs automatically builds help commands for your REPL using clap print_help:
% myapp
MyApp> 〉help
MyApp v0.1.0: My very cool app
COMMANDS:
append Append name to end of list
help Print this message or the help of the given subcommand(s)
prepend Prepend name to front of list
MyApp> 〉help append
append
Append name to end of list
USAGE:
append <name>
ARGS:
<name>
OPTIONS:
-h, --help Print help information
MyApp> 〉
§Errors
Your command functions don’t need to return reedline_repl_rs::Error
; you can return any error from
them. Your error will need to implement std::fmt::Display
, so the Repl can print the error,
and you’ll need to implement std::convert::From
for reedline_repl_rs::Error
to your error type.
This makes error handling in your command functions easier, since you can just allow whatever
errors your functions emit bubble up.
//! Example using Repl with a custom error type.
use reedline_repl_rs::clap::{ArgMatches, Command};
use reedline_repl_rs::Repl;
use std::fmt;
#[derive(Debug)]
enum CustomError {
ReplError(reedline_repl_rs::Error),
StringError(String),
}
impl From<reedline_repl_rs::Error> for CustomError {
fn from(e: reedline_repl_rs::Error) -> Self {
CustomError::ReplError(e)
}
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CustomError::ReplError(e) => write!(f, "REPL Error: {}", e),
CustomError::StringError(s) => write!(f, "String Error: {}", s),
}
}
}
impl std::error::Error for CustomError {}
/// Do nothing, unsuccesfully
fn hello<T>(_args: ArgMatches, _context: &mut T) -> Result<Option<String>, CustomError> {
Err(CustomError::StringError("Returning an error".to_string()))
}
fn main() -> Result<(), reedline_repl_rs::Error> {
let mut repl = Repl::new(())
.with_name("MyApp")
.with_version("v0.1.0")
.with_description("My very cool app")
.with_command(
Command::new("hello").about("Do nothing, unsuccessfully"),
hello,
);
repl.run()
}
Re-exports§
pub use clap;
pub use crossterm;
pub use nu_ansi_term;
pub use reedline;
pub use yansi;
Structs§
- Main REPL struct
Enums§
- Error type
Functions§
- Utility to format prompt strings as green and bold. Use yansi directly instead for custom colors.
- Utility to format prompt strings as yellow and bold. Use yansi directly instead for custom colors.
Type Aliases§
- AfterCommand callback function signature
- Command callback function signature
- Result type