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}