Crate repl_rs

Source
Expand description

repl-rs - REPL library for Rust

§Example

use std::collections::HashMap;
use repl_rs::{Command, Parameter, Result, Value};
use repl_rs::{Convert, Repl};

// Write "Hello"
fn hello<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>> {
    Ok(Some(format!("Hello, {}", args["who"])))
}

fn main() -> Result<()> {
    let mut repl = Repl::new(())
        .with_name("MyApp")
        .with_version("v0.1.0")
        .with_description("My very cool app")
        .add_command(
             Command::new("hello", hello)
                 .with_parameter(Parameter::new("who").set_required(true)?)?
                 .with_help("Greetings!"),
    );
    repl.run()
 }

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 HashMap of named arguments, contained in a Value struct, 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

§Conversions

The Value type has conversions defined for all the primitive types. Here’s how that works in practice:

use repl_rs::{Command, Parameter, Result, Value};
use repl_rs::{Convert, Repl};
use std::collections::HashMap;

// Add two numbers.
fn add<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>> {
    let first: i32 = args["first"].convert()?;
    let second: i32 = args["second"].convert()?;

    Ok(Some((first + second).to_string()))
}

fn main() -> Result<()> {
    let mut repl = Repl::new(())
        .with_name("MyApp")
        .with_version("v0.1.0")
        .with_description("My very cool app")
        .add_command(
            Command::new("add", add)
                .with_parameter(Parameter::new("first").set_required(true)?)?
                .with_parameter(Parameter::new("second").set_required(true)?)?
                .with_help("Add two numbers together"),
    );
    repl.run()
}

This example adds two numbers. The convert() function manages the conversion for you.

§Context

The Context type is used to keep state between REPL calls. Here’s an example:

use repl_rs::{Command, Parameter, Result, Value};
use repl_rs::{Convert, Repl};
use std::collections::{HashMap, VecDeque};

#[derive(Default)]
struct Context {
    list: VecDeque<String>,
}

// Append name to list
fn append(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
    let name: String = args["name"].convert()?;
    context.list.push_back(name);
    let list: Vec<String> = context.list.clone().into();

    Ok(Some(list.join(", ")))
}

// Prepend name to list
fn prepend(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
    let name: String = args["name"].convert()?;
    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())
        .add_command(
            Command::new("append", append)
                .with_parameter(Parameter::new("name").set_required(true)?)?
                .with_help("Append name to end of list"),
        )
        .add_command(
            Command::new("prepend", prepend)
                .with_parameter(Parameter::new("name").set_required(true)?)?
                .with_help("Prepend name to front of list"),
        );
    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 “initialize_repl” macro

Instead of hardcoding your package name, version and description in your code, you can instead use those values from your Cargo.toml file, using the initialize_repl macro:

#[macro_use]
extern crate clap;

use repl_rs::{initialize_repl, Convert, Repl};
use repl_rs::{Command, Parameter, Result, Value};
use std::collections::{HashMap, VecDeque};

/// Example using initialize_repl

#[derive(Default)]
struct Context {
    list: VecDeque<String>,
}

// Append name to list
fn append(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
    let name: String = args["name"].convert()?;
    context.list.push_back(name);
    let list: Vec<String> = context.list.clone().into();

    Ok(Some(list.join(", ")))
}

// Prepend name to list
fn prepend(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
    let name: String = args["name"].convert()?;
    context.list.push_front(name);
    let list: Vec<String> = context.list.clone().into();

    Ok(Some(list.join(", ")))
}

fn main() -> Result<()> {
    let mut repl = initialize_repl!(Context::default())
        .use_completion(true)
        .add_command(
            Command::new("append", append)
                .with_parameter(Parameter::new("name").set_required(true)?)?
                .with_help("Append name to end of list"),
        )
        .add_command(
            Command::new("prepend", prepend)
                .with_parameter(Parameter::new("name").set_required(true)?)?
                .with_help("Prepend name to front of list"),
        );
    repl.run()
}

Note the #[macro_use] extern crate clap at the top. You’ll need that in order to avoid getting messages like error: cannot find macro 'crate_name' in this scope.

§Help

repl-rs has support for supplying help commands for your REPL. This is accomplished through the HelpViewer, which is a trait that has a default implementation which should give you pretty much what you expect.

% myapp
Welcome to MyApp v0.1.0
MyApp> help
MyApp v0.1.0: My very cool app
------------------------------
append - Append name to end of list
prepend - Prepend name to front of list
MyApp> help append
append: Append name to end of list
Usage:
        append name
MyApp>

If you want to roll your own help, just implement HelpViewer and add it to your REPL using the .with_help_viewer() method.

§Errors

Your command functions don’t need to return 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 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.

use repl_rs::{Command, Parameter, Value};
use repl_rs::{Convert, Repl};
use std::collections::HashMap;
use std::fmt;
use std::result::Result;

// My custom error type
#[derive(Debug)]
enum Error {
    DivideByZeroError,
    ReplError(repl_rs::Error),
}

// Implement conversion from repl_rs::Error to my error type
impl From<repl_rs::Error> for Error {
    fn from(error: repl_rs::Error) -> Self {
        Error::ReplError(error)
    }
}

// My error has to implement Display as well
impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
        match self {
            Error::DivideByZeroError => write!(f, "Whoops, divided by zero!"),
            Error::ReplError(error) => write!(f, "{}", error),
        }
    }
}

// Divide two numbers.
fn divide<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>, Error> {
    let numerator: f32 = args["numerator"].convert()?;
    let denominator: f32 = args["denominator"].convert()?;

    if denominator == 0.0 {
        return Err(Error::DivideByZeroError);
    }

    Ok(Some((numerator / denominator).to_string()))
}

fn main() -> Result<(), Error> {
    let mut repl = Repl::new(())
        .with_name("MyApp")
        .with_version("v0.1.0")
        .with_description("My very cool app")
        .add_command(
            Command::new("divide", divide)
                .with_parameter(Parameter::new("numerator").set_required(true)?)?
                .with_parameter(Parameter::new("denominator").set_required(true)?)?
                .with_help("Divide two numbers"),
    );
    Ok(repl.run()?)
}

Modules§

builder
Define Command line arguments
parser
Command line argument parser

Macros§

arg
Create an Arg from a usage string.
command
Allows you to build the Command instance from your Cargo.toml at compile time.
crate_authors
Allows you to pull the authors for the command from your Cargo.toml at compile time in the form: "author1 lastname <author1@example.com>:author2 lastname <author2@example.com>"
crate_description
Allows you to pull the description from your Cargo.toml at compile time.
crate_name
Allows you to pull the name from your Cargo.toml at compile time.
crate_version
Allows you to pull the version from your Cargo.toml at compile time as MAJOR.MINOR.PATCH_PKGVERSION_PRE
initialize_repl
Initialize the name, version and description of the Repl from your crate name, version and description
value_parser
Select a ValueParser implementation from the intended type

Structs§

Arg
The abstract representation of a command line argument. Used to set all the options and relationships that define a valid argument for the program.
ArgGroup
Family of related arguments.
ArgMatches
Container for parse results.
Command
Struct to define a command in the REPL
HelpContext
Struct which gets sent to HelpViewer when help command is called
HelpEntry
Help entry which gets sent to HelpViewer when help for a particular command is requested
Id
Arg or ArgGroup identifier
Parameter
Command parameter
Repl
Main REPL struct
Value
Value type. Has conversions to every primitive type.

Enums§

ArgAction
Behavior of arguments when they are encountered while parsing
ColorChoice
Represents the color preferences for program output
Error
Error type
ValueHint
Provide shell with hint on how to complete an argument.

Traits§

Args
Parse a set of arguments into a user-defined container.
CommandFactory
Create a Command relevant for a user-defined container.
Convert
Trait to convert from a Value to some other type.
FromArgMatches
Converts an instance of ArgMatches to a user-defined container.
HelpViewer
Trait to be used if you want your own custom Help output
Parser
Parse command-line arguments into Self.
Subcommand
Parse a sub-command into a user-defined enum.
ValueEnum
Parse arguments into enums.

Type Aliases§

Callback
Command callback function signature
Result
Result type