repl_rs/
lib.rs

1//! repl-rs - [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) library
2//! for Rust
3//!
4//! # Example
5//!
6//! ```
7//! use std::collections::HashMap;
8//! use repl_rs::{Command, Parameter, Result, Value};
9//! use repl_rs::{Convert, Repl};
10//!
11//! // Write "Hello"
12//! fn hello<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>> {
13//!     Ok(Some(format!("Hello, {}", args["who"])))
14//! }
15//!
16//! fn main() -> Result<()> {
17//!     let mut repl = Repl::new(())
18//!         .with_name("MyApp")
19//!         .with_version("v0.1.0")
20//!         .with_description("My very cool app")
21//!         .add_command(
22//!              Command::new("hello", hello)
23//!                  .with_parameter(Parameter::new("who").set_required(true)?)?
24//!                  .with_help("Greetings!"),
25//!     );
26//!     repl.run()
27//!  }
28//! ```
29//! repl-rs uses the [builder](https://en.wikipedia.org/wiki/Builder_pattern) pattern extensively.
30//! What these lines are doing is:
31//! - creating a repl with an empty Context (see below)
32//! - with a name of "MyApp", the given version, and the given description
33//! - and adding a "hello" command which calls out to the `hello` callback function defined above
34//! - the `hello` command has a single parameter, "who", which is required, and has the given help
35//! message
36//!
37//! The `hello` function takes a HashMap of named arguments, contained in a
38//! [Value](struct.Value.html) struct, and an (unused) `Context`, which is used to hold state if you
39//! need to - the initial context is passed in to the call to
40//! [Repl::new](struct.Repl.html#method.new), in our case, `()`.
41//! Because we're not using a Context, we need to include a generic type in our `hello` function,
42//! because there's no way to pass an argument of type `()` otherwise.
43//!
44//! All command function callbacks return a `Result<Option<String>>`. This has the following
45//! effect:
46//! - If the return is `Ok(Some(String))`, it prints the string to stdout
47//! - If the return is `Ok(None)`, it prints nothing
48//! - If the return is an error, it prints the error message to stderr
49//!
50//! # Conversions
51//!
52//! The [Value](struct.Value.html) type has conversions defined for all the primitive types. Here's
53//! how that works in practice:
54//! ```
55//! use repl_rs::{Command, Parameter, Result, Value};
56//! use repl_rs::{Convert, Repl};
57//! use std::collections::HashMap;
58//!
59//! // Add two numbers.
60//! fn add<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>> {
61//!     let first: i32 = args["first"].convert()?;
62//!     let second: i32 = args["second"].convert()?;
63//!
64//!     Ok(Some((first + second).to_string()))
65//! }
66//!
67//! fn main() -> Result<()> {
68//!     let mut repl = Repl::new(())
69//!         .with_name("MyApp")
70//!         .with_version("v0.1.0")
71//!         .with_description("My very cool app")
72//!         .add_command(
73//!             Command::new("add", add)
74//!                 .with_parameter(Parameter::new("first").set_required(true)?)?
75//!                 .with_parameter(Parameter::new("second").set_required(true)?)?
76//!                 .with_help("Add two numbers together"),
77//!     );
78//!     repl.run()
79//! }
80//! ```
81//! This example adds two numbers. The `convert()` function manages the conversion for you.
82//!
83//! # Context
84//!
85//! The `Context` type is used to keep state between REPL calls. Here's an example:
86//! ```
87//! use repl_rs::{Command, Parameter, Result, Value};
88//! use repl_rs::{Convert, Repl};
89//! use std::collections::{HashMap, VecDeque};
90//!
91//! #[derive(Default)]
92//! struct Context {
93//!     list: VecDeque<String>,
94//! }
95//!
96//! // Append name to list
97//! fn append(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
98//!     let name: String = args["name"].convert()?;
99//!     context.list.push_back(name);
100//!     let list: Vec<String> = context.list.clone().into();
101//!
102//!     Ok(Some(list.join(", ")))
103//! }
104//!
105//! // Prepend name to list
106//! fn prepend(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
107//!     let name: String = args["name"].convert()?;
108//!     context.list.push_front(name);
109//!     let list: Vec<String> = context.list.clone().into();
110//!
111//!     Ok(Some(list.join(", ")))
112//! }
113//!
114//! fn main() -> Result<()> {
115//!     let mut repl = Repl::new(Context::default())
116//!         .add_command(
117//!             Command::new("append", append)
118//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
119//!                 .with_help("Append name to end of list"),
120//!         )
121//!         .add_command(
122//!             Command::new("prepend", prepend)
123//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
124//!                 .with_help("Prepend name to front of list"),
125//!         );
126//!     repl.run()
127//! }
128//! ```
129//! A few things to note:
130//! - you pass in the initial value for your Context struct to the call to
131//! [Repl::new()](struct.Repl.html#method.new)
132//! - the context is passed to your command callback functions as a mutable reference
133//!
134//! # The "initialize_repl" macro
135//! Instead of hardcoding your package name, version and description in your code, you can instead
136//! use those values from your `Cargo.toml` file, using the `initialize_repl` macro:
137//! ```
138//! #[macro_use]
139//! extern crate clap;
140//!
141//! use repl_rs::{initialize_repl, Convert, Repl};
142//! use repl_rs::{Command, Parameter, Result, Value};
143//! use std::collections::{HashMap, VecDeque};
144//!
145//! /// Example using initialize_repl
146//!
147//! #[derive(Default)]
148//! struct Context {
149//!     list: VecDeque<String>,
150//! }
151//!
152//! // Append name to list
153//! fn append(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
154//!     let name: String = args["name"].convert()?;
155//!     context.list.push_back(name);
156//!     let list: Vec<String> = context.list.clone().into();
157//!
158//!     Ok(Some(list.join(", ")))
159//! }
160//!
161//! // Prepend name to list
162//! fn prepend(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
163//!     let name: String = args["name"].convert()?;
164//!     context.list.push_front(name);
165//!     let list: Vec<String> = context.list.clone().into();
166//!
167//!     Ok(Some(list.join(", ")))
168//! }
169//!
170//! fn main() -> Result<()> {
171//!     let mut repl = initialize_repl!(Context::default())
172//!         .use_completion(true)
173//!         .add_command(
174//!             Command::new("append", append)
175//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
176//!                 .with_help("Append name to end of list"),
177//!         )
178//!         .add_command(
179//!             Command::new("prepend", prepend)
180//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
181//!                 .with_help("Prepend name to front of list"),
182//!         );
183//!     repl.run()
184//! }
185//! ```
186//! Note the `#[macro_use] extern crate clap` at the top. You'll need that in order to avoid
187//! getting messages like `error: cannot find macro 'crate_name' in this scope`.
188//!
189//! # Help
190//! repl-rs has support for supplying help commands for your REPL. This is accomplished through the
191//! [HelpViewer](trait.HelpViewer.html), which is a trait that has a default implementation which should give you pretty
192//! much what you expect.
193//! ```bash
194//! % myapp
195//! Welcome to MyApp v0.1.0
196//! MyApp> help
197//! MyApp v0.1.0: My very cool app
198//! ------------------------------
199//! append - Append name to end of list
200//! prepend - Prepend name to front of list
201//! MyApp> help append
202//! append: Append name to end of list
203//! Usage:
204//!         append name
205//! MyApp>
206//! ```
207//! If you want to roll your own help, just implement [HelpViewer](trait.HelpViewer.html) and add it to your REPL using the
208//! [.with_help_viewer()](struct.Repl.html#method.with_help_viewer) method.
209//!
210//! # Errors
211//!
212//! Your command functions don't need to return `repl_rs::Error`; you can return any error from
213//! them. Your error will need to implement `std::fmt::Display`, so the Repl can print the error,
214//! and you'll need to implement `std::convert::From` for `repl_rs::Error` to your error type.
215//! This makes error handling in your command functions easier, since you can just allow whatever
216//! errors your functions emit bubble up.
217//!
218//! ```
219//! use repl_rs::{Command, Parameter, Value};
220//! use repl_rs::{Convert, Repl};
221//! use std::collections::HashMap;
222//! use std::fmt;
223//! use std::result::Result;
224//!
225//! // My custom error type
226//! #[derive(Debug)]
227//! enum Error {
228//!     DivideByZeroError,
229//!     ReplError(repl_rs::Error),
230//! }
231//!
232//! // Implement conversion from repl_rs::Error to my error type
233//! impl From<repl_rs::Error> for Error {
234//!     fn from(error: repl_rs::Error) -> Self {
235//!         Error::ReplError(error)
236//!     }
237//! }
238//!
239//! // My error has to implement Display as well
240//! impl fmt::Display for Error {
241//!     fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
242//!         match self {
243//!             Error::DivideByZeroError => write!(f, "Whoops, divided by zero!"),
244//!             Error::ReplError(error) => write!(f, "{}", error),
245//!         }
246//!     }
247//! }
248//!
249//! // Divide two numbers.
250//! fn divide<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>, Error> {
251//!     let numerator: f32 = args["numerator"].convert()?;
252//!     let denominator: f32 = args["denominator"].convert()?;
253//!
254//!     if denominator == 0.0 {
255//!         return Err(Error::DivideByZeroError);
256//!     }
257//!
258//!     Ok(Some((numerator / denominator).to_string()))
259//! }
260//!
261//! fn main() -> Result<(), Error> {
262//!     let mut repl = Repl::new(())
263//!         .with_name("MyApp")
264//!         .with_version("v0.1.0")
265//!         .with_description("My very cool app")
266//!         .add_command(
267//!             Command::new("divide", divide)
268//!                 .with_parameter(Parameter::new("numerator").set_required(true)?)?
269//!                 .with_parameter(Parameter::new("denominator").set_required(true)?)?
270//!                 .with_help("Divide two numbers"),
271//!     );
272//!     Ok(repl.run()?)
273//! }
274//! ```
275//!
276mod command;
277mod error;
278mod help;
279mod parameter;
280mod repl;
281mod value;
282
283pub use clap::*;
284pub use command::Command;
285pub use error::{Error, Result};
286#[doc(inline)]
287pub use help::{HelpContext, HelpEntry, HelpViewer};
288pub use parameter::Parameter;
289#[doc(inline)]
290pub use repl::Repl;
291#[doc(inline)]
292pub use value::{Convert, Value};
293
294use std::collections::HashMap;
295
296/// Command callback function signature
297pub type Callback<Context, Error> =
298    fn(HashMap<String, Value>, &mut Context) -> std::result::Result<Option<String>, Error>;
299
300/// Initialize the name, version and description of the Repl from your crate name, version and
301/// description
302#[macro_export]
303macro_rules! initialize_repl {
304    ($context: expr) => {{
305        let repl = Repl::new($context)
306            .with_name(crate_name!())
307            .with_version(crate_version!())
308            .with_description(crate_description!());
309
310        repl
311    }};
312}