Skip to main content

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//! # Calling async functions
51//!
52//! Command functions are defined as non-async, but if you need to call an async function from your
53//! REPL, you'll have to do a little more work.
54//!
55//! ```
56//! use repl_rs::{Command, Parameter, Result, Value};
57//! use repl_rs::{Convert, Repl};
58//! use std::collections::HashMap;
59//! use tokio::runtime::Runtime;
60//! 
61//! // Async function being called. Silly example, but you get the point
62//! async fn add_async(first: i32, second: i32) -> Result<i32> {
63//!     Ok(first + second)
64//! }
65//! 
66//! // Add two numbers. Calls the async fn using Runtime::block_on()
67//! fn add<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>> {
68//!     let first: i32 = args["first"].convert()?;
69//!     let second: i32 = args["second"].convert()?;
70//!
71//!     // If you're calling multiple async functions, you should
72//!     // probably share the runtime globally
73//!     let runtime = Runtime::new().unwrap();
74//!
75//!     // Call block_on() on the runtime to run the async code,
76//!     // wrapping it in an async block
77//!     runtime.block_on(
78//!         async move {
79//!             add_async(first, second).await.map(|i| Some(i.to_string()))
80//!         }
81//!     )
82//! }
83//! 
84//! fn main() -> Result<()> {
85//!     let mut repl = Repl::new(())
86//!         .with_name("MyApp")
87//!         .with_version("v0.1.0")
88//!         .with_description("My very cool app")
89//!         .add_command(
90//!             Command::new("add", add)
91//!                 .with_parameter(Parameter::new("first").set_required(true)?)?
92//!                 .with_parameter(Parameter::new("second").set_required(true)?)?
93//!                 .with_help("Add two numbers together"),
94//!         );
95//!     repl.run()
96//! }
97//! ```
98//! You can try this example out in the repo.
99//!
100//! # Conversions
101//!
102//! The [Value](struct.Value.html) type has conversions defined for all the primitive types. Here's
103//! how that works in practice:
104//! ```
105//! use repl_rs::{Command, Parameter, Result, Value};
106//! use repl_rs::{Convert, Repl};
107//! use std::collections::HashMap;
108//!
109//! // Add two numbers.
110//! fn add<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>> {
111//!     let first: i32 = args["first"].convert()?;
112//!     let second: i32 = args["second"].convert()?;
113//!
114//!     Ok(Some((first + second).to_string()))
115//! }
116//!
117//! fn main() -> Result<()> {
118//!     let mut repl = Repl::new(())
119//!         .with_name("MyApp")
120//!         .with_version("v0.1.0")
121//!         .with_description("My very cool app")
122//!         .add_command(
123//!             Command::new("add", add)
124//!                 .with_parameter(Parameter::new("first").set_required(true)?)?
125//!                 .with_parameter(Parameter::new("second").set_required(true)?)?
126//!                 .with_help("Add two numbers together"),
127//!     );
128//!     repl.run()
129//! }
130//! ```
131//! This example adds two numbers. The `convert()` function manages the conversion for you.
132//!
133//! # Context
134//!
135//! The `Context` type is used to keep state between REPL calls. Here's an example:
136//! ```
137//! use repl_rs::{Command, Parameter, Result, Value};
138//! use repl_rs::{Convert, Repl};
139//! use std::collections::{HashMap, VecDeque};
140//!
141//! #[derive(Default)]
142//! struct Context {
143//!     list: VecDeque<String>,
144//! }
145//!
146//! // Append name to list
147//! fn append(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
148//!     let name: String = args["name"].convert()?;
149//!     context.list.push_back(name);
150//!     let list: Vec<String> = context.list.clone().into();
151//!
152//!     Ok(Some(list.join(", ")))
153//! }
154//!
155//! // Prepend name to list
156//! fn prepend(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
157//!     let name: String = args["name"].convert()?;
158//!     context.list.push_front(name);
159//!     let list: Vec<String> = context.list.clone().into();
160//!
161//!     Ok(Some(list.join(", ")))
162//! }
163//!
164//! fn main() -> Result<()> {
165//!     let mut repl = Repl::new(Context::default())
166//!         .add_command(
167//!             Command::new("append", append)
168//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
169//!                 .with_help("Append name to end of list"),
170//!         )
171//!         .add_command(
172//!             Command::new("prepend", prepend)
173//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
174//!                 .with_help("Prepend name to front of list"),
175//!         );
176//!     repl.run()
177//! }
178//! ```
179//! A few things to note:
180//! - you pass in the initial value for your Context struct to the call to
181//!   [Repl::new()](struct.Repl.html#method.new)
182//! - the context is passed to your command callback functions as a mutable reference
183//!
184//! # The "initialize_repl" macro
185//! Instead of hardcoding your package name, version and description in your code, you can instead
186//! use those values from your `Cargo.toml` file, using the `initialize_repl` macro:
187//! ```
188//! #[macro_use]
189//! extern crate clap;
190//!
191//! use repl_rs::{initialize_repl, Convert, Repl};
192//! use repl_rs::{Command, Parameter, Result, Value};
193//! use std::collections::{HashMap, VecDeque};
194//!
195//! /// Example using initialize_repl
196//!
197//! #[derive(Default)]
198//! struct Context {
199//!     list: VecDeque<String>,
200//! }
201//!
202//! // Append name to list
203//! fn append(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
204//!     let name: String = args["name"].convert()?;
205//!     context.list.push_back(name);
206//!     let list: Vec<String> = context.list.clone().into();
207//!
208//!     Ok(Some(list.join(", ")))
209//! }
210//!
211//! // Prepend name to list
212//! fn prepend(args: HashMap<String, Value>, context: &mut Context) -> Result<Option<String>> {
213//!     let name: String = args["name"].convert()?;
214//!     context.list.push_front(name);
215//!     let list: Vec<String> = context.list.clone().into();
216//!
217//!     Ok(Some(list.join(", ")))
218//! }
219//!
220//! fn main() -> Result<()> {
221//!     let mut repl = initialize_repl!(Context::default())
222//!         .use_completion(true)
223//!         .add_command(
224//!             Command::new("append", append)
225//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
226//!                 .with_help("Append name to end of list"),
227//!         )
228//!         .add_command(
229//!             Command::new("prepend", prepend)
230//!                 .with_parameter(Parameter::new("name").set_required(true)?)?
231//!                 .with_help("Prepend name to front of list"),
232//!         );
233//!     repl.run()
234//! }
235//! ```
236//! Note the `#[macro_use] extern crate clap` at the top. You'll need that in order to avoid
237//! getting messages like `error: cannot find macro 'crate_name' in this scope`.
238//!
239//! # Help
240//! repl-rs has support for supplying help commands for your REPL. This is accomplished through the
241//! [HelpViewer](trait.HelpViewer.html), which is a trait that has a default implementation which should give you pretty
242//! much what you expect.
243//! ```bash
244//! % myapp
245//! Welcome to MyApp v0.1.0
246//! MyApp> help
247//! MyApp v0.1.0: My very cool app
248//! ------------------------------
249//! append - Append name to end of list
250//! prepend - Prepend name to front of list
251//! MyApp> help append
252//! append: Append name to end of list
253//! Usage:
254//!         append name
255//! MyApp>
256//! ```
257//! If you want to roll your own help, just implement [HelpViewer](trait.HelpViewer.html) and add it to your REPL using the
258//! [.with_help_viewer()](struct.Repl.html#method.with_help_viewer) method.
259//!
260//! # Errors
261//!
262//! Your command functions don't need to return `repl_rs::Error`; you can return any error from
263//! them. Your error will need to implement `std::fmt::Display`, so the Repl can print the error,
264//! and you'll need to implement `std::convert::From` for `repl_rs::Error` to your error type.
265//! This makes error handling in your command functions easier, since you can just allow whatever
266//! errors your functions emit bubble up.
267//!
268//! ```
269//! use repl_rs::{Command, Parameter, Value};
270//! use repl_rs::{Convert, Repl};
271//! use std::collections::HashMap;
272//! use std::fmt;
273//! use std::result::Result;
274//!
275//! // My custom error type
276//! #[derive(Debug)]
277//! enum Error {
278//!     DivideByZeroError,
279//!     ReplError(repl_rs::Error),
280//! }
281//!
282//! // Implement conversion from repl_rs::Error to my error type
283//! impl From<repl_rs::Error> for Error {
284//!     fn from(error: repl_rs::Error) -> Self {
285//!         Error::ReplError(error)
286//!     }
287//! }
288//!
289//! // My error has to implement Display as well
290//! impl fmt::Display for Error {
291//!     fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
292//!         match self {
293//!             Error::DivideByZeroError => write!(f, "Whoops, divided by zero!"),
294//!             Error::ReplError(error) => write!(f, "{}", error),
295//!         }
296//!     }
297//! }
298//!
299//! // Divide two numbers.
300//! fn divide<T>(args: HashMap<String, Value>, _context: &mut T) -> Result<Option<String>, Error> {
301//!     let numerator: f32 = args["numerator"].convert()?;
302//!     let denominator: f32 = args["denominator"].convert()?;
303//!
304//!     if denominator == 0.0 {
305//!         return Err(Error::DivideByZeroError);
306//!     }
307//!
308//!     Ok(Some((numerator / denominator).to_string()))
309//! }
310//!
311//! fn main() -> Result<(), Error> {
312//!     let mut repl = Repl::new(())
313//!         .with_name("MyApp")
314//!         .with_version("v0.1.0")
315//!         .with_description("My very cool app")
316//!         .add_command(
317//!             Command::new("divide", divide)
318//!                 .with_parameter(Parameter::new("numerator").set_required(true)?)?
319//!                 .with_parameter(Parameter::new("denominator").set_required(true)?)?
320//!                 .with_help("Divide two numbers"),
321//!     );
322//!     Ok(repl.run()?)
323//! }
324//! ```
325//!
326mod command;
327mod error;
328mod help;
329mod parameter;
330mod repl;
331mod value;
332
333pub use command::Command;
334pub use error::{Error, Result};
335#[doc(inline)]
336pub use help::{HelpContext, HelpEntry, HelpViewer};
337pub use parameter::Parameter;
338#[doc(inline)]
339pub use repl::Repl;
340#[doc(inline)]
341pub use value::{Convert, Value};
342
343use std::collections::HashMap;
344
345/// Command callback function signature
346pub type Callback<Context, Error> =
347    fn(HashMap<String, Value>, &mut Context) -> std::result::Result<Option<String>, Error>;
348
349/// Initialize the name, version and description of the Repl from your crate name, version and
350/// description
351#[macro_export]
352macro_rules! initialize_repl {
353    ($context: expr) => {{
354        let repl = Repl::new($context)
355            .with_name(crate_name!())
356            .with_version(crate_version!())
357            .with_description(crate_description!());
358
359        repl
360    }};
361}