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}