requestty/prompt_module.rs
1use ui::{backend::Backend, events::EventIterator};
2
3use crate::{Answer, Answers, Question};
4
5/// A collection of questions and answers for previously answered questions.
6///
7/// Unlike [`prompt`], this allows you to control how many questions you want to ask, and ask with
8/// previous answers as well.
9///
10/// [`prompt`]: crate::prompt()
11#[derive(Debug, Clone, PartialEq)]
12pub struct PromptModule<Q> {
13 questions: Q,
14 answers: Answers,
15}
16
17impl<'a, Q> PromptModule<Q>
18where
19 Q: Iterator<Item = Question<'a>>,
20{
21 /// Creates a new `PromptModule` with the given questions
22 pub fn new<I>(questions: I) -> Self
23 where
24 I: IntoIterator<IntoIter = Q, Item = Question<'a>>,
25 {
26 Self {
27 answers: Answers::default(),
28 questions: questions.into_iter(),
29 }
30 }
31
32 /// Creates a `PromptModule` with the given questions and answers
33 pub fn with_answers(mut self, answers: Answers) -> Self {
34 self.answers = answers;
35 self
36 }
37
38 /// Prompt a single question with the default [`Backend`] and [`EventIterator`].
39 ///
40 /// This may or may not actually prompt the question based on what `when` and `ask_if_answered`
41 /// returns for that particular question.
42 #[cfg(any(feature = "crossterm", feature = "termion"))]
43 #[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
44 pub fn prompt(&mut self) -> crate::Result<Option<&mut Answer>> {
45 let stdout = std::io::stdout();
46 let mut stdout = ui::backend::get_backend(stdout.lock());
47
48 self.prompt_with(&mut stdout, &mut ui::events::get_events())
49 }
50
51 /// Prompt a single question with the given [`Backend`] and [`EventIterator`].
52 ///
53 /// This may or may not actually prompt the question based on what `when` and `ask_if_answered`
54 /// returns for that particular question.
55 pub fn prompt_with<B, E>(
56 &mut self,
57 backend: &mut B,
58 events: &mut E,
59 ) -> crate::Result<Option<&mut Answer>>
60 where
61 B: Backend,
62 E: EventIterator,
63 {
64 for question in self.questions.by_ref() {
65 if let Some((name, answer)) = question.ask(&self.answers, backend, events)? {
66 return Ok(Some(self.answers.insert(name, answer)));
67 }
68 }
69
70 Ok(None)
71 }
72
73 /// Prompt all remaining questions with the default [`Backend`] and [`EventIterator`].
74 ///
75 /// It consumes `self` and returns the answers to all the questions asked.
76 #[cfg(any(feature = "crossterm", feature = "termion"))]
77 #[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
78 pub fn prompt_all(self) -> crate::Result<Answers> {
79 let stdout = std::io::stdout();
80 let mut stdout = ui::backend::get_backend(stdout.lock());
81 let mut events = ui::events::get_events();
82
83 self.prompt_all_with(&mut stdout, &mut events)
84 }
85
86 /// Prompt all remaining questions with the given [`Backend`] and [`EventIterator`].
87 ///
88 /// It consumes `self` and returns the answers to all the questions asked.
89 pub fn prompt_all_with<B, E>(
90 mut self,
91 backend: &mut B,
92 events: &mut E,
93 ) -> crate::Result<Answers>
94 where
95 B: Backend,
96 E: EventIterator,
97 {
98 self.answers.reserve(self.questions.size_hint().0);
99
100 while self.prompt_with(backend, events)?.is_some() {}
101
102 Ok(self.answers)
103 }
104
105 /// Consumes `self` returning the answers to the previously asked questions.
106 pub fn into_answers(self) -> Answers {
107 self.answers
108 }
109}
110
111/// A macro to easily write a [`PromptModule`].
112///
113/// # Usage
114///
115/// You can specify questions similar to a `vec![]` of struct instantiations. Each field corresponds
116/// to calling the builder method of the same name for the respective question kind.
117/// ```
118/// # let some_variable = "message";
119/// # let when = true;
120/// # fn get_default() -> bool { true }
121/// use requestty::prompt_module;
122///
123/// let prompt_module = prompt_module![
124/// MultiSelect {
125/// // Each field takes a value, which can result in anything that implements `Into`
126/// // for the required type
127/// name: "name",
128/// // The value can be any expression, for example a local variable.
129/// message: some_variable,
130/// // If the field name and the variable name are the same, this shorthand can be
131/// // used.
132/// when,
133/// // While most values are generic expressions, if a array literal is passed to
134/// // choices, some special syntax applies.
135/// // - For `MultiSelect`, default can be specified
136/// // - For `OrderSelect`, separators cannot be specified
137/// choices: [
138/// // By default array entries are taken as `Choice(_)`s.
139/// "Choice 1",
140/// // For `MultiSelect` if the word 'default' follows the initial expression, a
141/// // default can be set for that choice.
142/// "Choice 2" default get_default(),
143/// // The word 'separator' or 'sep' can be used to create separators. If no
144/// // expression is given along with 'separator', it is taken as a `DefaultSeparator`.
145/// separator,
146/// // Otherwise if there is an expression, it is taken as a `Separator(_)`,
147/// sep "Separator text!",
148/// ],
149/// },
150/// ];
151/// ```
152///
153/// # Inline
154///
155/// By default, the questions are stored in a [`Vec`]. However, if you wish to store the questions
156/// on the stack, prefix the questions with `inline`:
157/// ```
158/// use requestty::prompt_module;
159///
160/// let prompt_module = prompt_module![ inline
161/// Input {
162/// name: "input"
163/// },
164/// ];
165/// ```
166///
167/// Note that inlining only works for rust version 1.51 onwards. Pre 1.51, a [`Vec`] is still used.
168///
169/// See also [`questions`].
170///
171/// [`questions`]: crate::questions
172#[cfg(feature = "macros")]
173#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
174#[macro_export]
175macro_rules! prompt_module {
176 ($($tt:tt)*) => {
177 $crate::PromptModule::new($crate::questions! [ $($tt)* ])
178 };
179}