requestty/lib.rs
1//! `requestty` (request-tty) is an easy-to-use collection of interactive cli prompts inspired by
2//! [Inquirer.js].
3//!
4//! [Inquirer.js]: https://github.com/SBoudrias/Inquirer.js/
5//!
6//! # Questions
7//!
8//! This crate is based on creating [`Question`]s, and then prompting them to the user. There are 11
9//! in-built [`Question`]s, but if none of them fit your need, you can [create your own!](#custom-prompts)
10//!
11//! There are 2 ways of creating [`Question`]s.
12//!
13//! ### Using builders
14//!
15//! ```
16//! use requestty::{Question, Answers};
17//!
18//! let question = Question::expand("toppings")
19//! .message("What toppings do you want?")
20//! .when(|answers: &Answers| !answers["custom_toppings"].as_bool().unwrap())
21//! .choice('p', "Pepperoni and cheese")
22//! .choice('a', "All dressed")
23//! .choice('w', "Hawaiian")
24//! .build();
25//! ```
26//!
27//! See [`Question`] for more information on the builders.
28//!
29//! ### Using macros (only with `macros` feature)
30//!
31//! Unlike the builder api, the macros can only be used to create a list of questions.
32//!
33#![cfg_attr(feature = "macros", doc = "```")]
34#![cfg_attr(not(feature = "macros"), doc = "```ignore")]
35//! use requestty::{questions, Answers};
36//!
37//! let questions = questions! [
38//! Expand {
39//! name: "toppings",
40//! message: "What toppings do you want?",
41//! when: |answers: &Answers| !answers["custom_toppings"].as_bool().unwrap(),
42//! choices: [
43//! ('p', "Pepperoni and cheese"),
44//! ('a', "All dressed"),
45//! ('w', "Hawaiian"),
46//! ]
47//! }
48//! ];
49//! ```
50//!
51//! See [`questions`] and [`prompt_module`](prompt_module!) for more information on the macros.
52//!
53//! ### Prompting
54//!
55//! [`Question`]s can be asked in 2 main ways.
56//!
57//! - Using direct [functions](#functions) provided by the crate.
58//! ```no_run
59//! let questions = vec![
60//! // Declare the questions you want to ask
61//! ];
62//!
63//! let answers = requestty::prompt(questions)?;
64//! # Result::<_, requestty::ErrorKind>::Ok(())
65//! ```
66//!
67//! - Using [`PromptModule`]
68//! ```no_run
69//! use requestty::PromptModule;
70//!
71//! let questions = PromptModule::new(vec![
72//! // Declare the questions you want to ask
73//! ]);
74//!
75//! let answers = questions.prompt_all()?;
76//! # Result::<_, requestty::ErrorKind>::Ok(())
77//! ```
78//! This is mainly useful if you need more control over prompting the questions, and using
79//! previous [`Answers`].
80//!
81//! See the documentation of [`Question`] for more information on the different in-built questions.
82//!
83//! # Terminal Interaction
84//!
85//! Terminal interaction is handled by 2 traits: [`Backend`] and [`EventIterator`].
86//!
87//! The traits are already implemented and can be enabled with features for the following terminal
88//! libraries:
89//! - [`crossterm`](https://crates.io/crates/crossterm) (default)
90//! - [`termion`](https://crates.io/crates/termion)
91//!
92//! The default is `crossterm` for the following reasons:
93//! - Wider terminal support
94//! - Better event processing (in my experience)
95//!
96//! [`Backend`]: prompt::Backend
97//! [`EventIterator`]: prompt::EventIterator
98//!
99//! # Custom Prompts
100//!
101//! If the crate's in-built prompts does not satisfy your needs, you can build your own custom
102//! prompts using the [`Prompt`](question::Prompt) trait.
103//!
104//! # Optional features
105//!
106//! - `macros`: Enabling this feature will allow you to use the [`questions`] and
107//! [`prompt_module`](prompt_module!) macros.
108//!
109//! - `smallvec` (default): Enabling this feature will use [`SmallVec`] instead of [`Vec`] for [auto
110//! completions]. This allows inlining single completions.
111//!
112//! - `crossterm` (default): Enabling this feature will use the [`crossterm`](https://crates.io/crates/crossterm)
113//! library for terminal interactions such as drawing and receiving events.
114//!
115//! - `termion`: Enabling this feature will use the [`termion`](https://crates.io/crates/termion)
116//! library for terminal interactions such as drawing and receiving events.
117//!
118//! [`SmallVec`]: https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html
119//! [auto completions]: crate::question::InputBuilder::auto_complete
120//!
121//! # Examples
122//!
123//! ```no_run
124//! use requestty::Question;
125//!
126//! let password = Question::password("password")
127//! .message("What is your password?")
128//! .mask('*')
129//! .build();
130//!
131//! let answer = requestty::prompt_one(password)?;
132//!
133//! println!("Your password was: {}", answer.as_string().expect("password returns a string"));
134//! # Result::<_, requestty::ErrorKind>::Ok(())
135//! ```
136//!
137//! For more examples, see the documentation for the various in-built questions, and the
138//! [`examples`] directory.
139//!
140//! [`examples`]: https://github.com/lutetium-vanadium/requestty/tree/master/examples
141#![deny(
142 missing_docs,
143 missing_debug_implementations,
144 unreachable_pub,
145 rustdoc::broken_intra_doc_links
146)]
147#![warn(rust_2018_idioms)]
148#![cfg_attr(docsrs, feature(doc_cfg))]
149
150mod answer;
151mod prompt_module;
152pub mod question;
153
154use ui::{backend::Backend, events::EventIterator};
155
156/// A macro to easily write an iterator of [`Question`]s.
157///
158/// [`Question`]: crate::Question
159///
160/// # Usage
161///
162/// You can specify questions similar to a `vec![]` of struct instantiations. Each field corresponds
163/// to calling the builder method of the same name for the respective question kind.
164/// ```
165/// # let some_variable = "message";
166/// # let when = true;
167/// # fn get_default() -> bool { true }
168/// use requestty::questions;
169///
170/// let questions = questions![
171/// MultiSelect {
172/// // Each field takes a value, which can result in anything that implements `Into`
173/// // for the required type
174/// name: "name",
175/// // The value can be any expression, for example a local variable.
176/// message: some_variable,
177/// // If the field name and the variable name are the same, this shorthand can be
178/// // used.
179/// when,
180/// // While most values are generic expressions, if a array literal is passed to
181/// // choices, some special syntax applies.
182/// // - For `MultiSelect`, default can be specified
183/// // - For `OrderSelect`, separators cannot be specified
184/// choices: [
185/// // By default array entries are taken as `Choice(_)`s.
186/// "Choice 1",
187/// // For `MultiSelect` if the word 'default' follows the initial expression, a
188/// // default can be set for that choice.
189/// "Choice 2" default get_default(),
190/// // The word 'separator' or 'sep' can be used to create separators. If no
191/// // expression is given along with 'separator', it is taken as a `DefaultSeparator`.
192/// separator,
193/// // Otherwise if there is an expression, it is taken as a `Separator(_)`,
194/// sep "Separator text!",
195/// ],
196/// },
197/// ];
198/// ```
199///
200/// # Inline
201///
202/// By default, the questions are stored in a [`Vec`]. However, if you wish to store the questions
203/// on the stack, prefix the questions with `inline`:
204/// ```
205/// use requestty::questions;
206///
207/// let questions = questions! [ inline
208/// Input {
209/// name: "input"
210/// },
211/// ];
212/// ```
213///
214/// Note that inlining only works for rust version 1.51 onwards. Pre 1.51, a [`Vec`] is still used.
215///
216/// See also [`prompt_module`](prompt_module!).
217#[cfg(feature = "macros")]
218#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
219pub use r#macro::questions;
220
221pub use answer::{Answer, Answers, ExpandItem, ListItem};
222pub use prompt_module::PromptModule;
223pub use question::{Choice::Choice, Choice::DefaultSeparator, Choice::Separator, Question};
224pub use ui::{symbols, ErrorKind, OnEsc, Result};
225
226/// A module that re-exports all the things required for writing custom [`Prompt`]s.
227///
228/// [`Prompt`]: prompt::Prompt
229pub mod prompt {
230 pub use crate::{question::Prompt, Answer, Answers};
231 pub use ui::{
232 backend::{self, Backend},
233 events::{self, EventIterator},
234 style,
235 };
236}
237
238/// Prompt all the questions in the given iterator, with the default [`Backend`] and [`EventIterator`].
239#[cfg(any(feature = "crossterm", feature = "termion"))]
240#[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
241pub fn prompt<'a, Q>(questions: Q) -> Result<Answers>
242where
243 Q: IntoIterator<Item = Question<'a>>,
244{
245 PromptModule::new(questions).prompt_all()
246}
247
248/// Prompt the given question, with the default [`Backend`] and [`EventIterator`].
249///
250/// # Panics
251///
252/// This will panic if `when` on the [`Question`] prevents the question from being asked.
253#[cfg(any(feature = "crossterm", feature = "termion"))]
254#[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
255pub fn prompt_one<'a, I: Into<Question<'a>>>(question: I) -> Result<Answer> {
256 let stdout = std::io::stdout();
257 let mut stdout = ui::backend::get_backend(stdout.lock());
258 let mut events = ui::events::get_events();
259
260 prompt_one_with(question.into(), &mut stdout, &mut events)
261}
262
263/// Prompt all the questions in the given iterator, with the given [`Backend`] and [`EventIterator`].
264pub fn prompt_with<'a, Q, B, E>(questions: Q, backend: &mut B, events: &mut E) -> Result<Answers>
265where
266 Q: IntoIterator<Item = Question<'a>>,
267 B: Backend,
268 E: EventIterator,
269{
270 PromptModule::new(questions).prompt_all_with(backend, events)
271}
272
273/// Prompt the given question, with the given [`Backend`] and [`EventIterator`].
274///
275/// # Panics
276///
277/// This will panic if `when` on the [`Question`] prevents the question from being asked.
278pub fn prompt_one_with<'a, Q, B, E>(question: Q, backend: &mut B, events: &mut E) -> Result<Answer>
279where
280 Q: Into<Question<'a>>,
281 B: Backend,
282 E: EventIterator,
283{
284 let ans = question.into().ask(&Answers::default(), backend, events)?;
285
286 Ok(ans.expect("The question wasn't asked").1)
287}