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}