1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use ui::{backend::Backend, events::EventIterator};

use crate::{Answer, Answers, Question};

/// A collection of questions and answers for previously answered questions.
///
/// Unlike [`prompt`], this allows you to control how many questions you want to ask, and ask with
/// previous answers as well.
///
/// [`prompt`]: crate::prompt()
#[derive(Debug, Clone, PartialEq)]
pub struct PromptModule<Q> {
    questions: Q,
    answers: Answers,
}

impl<'a, Q> PromptModule<Q>
where
    Q: Iterator<Item = Question<'a>>,
{
    /// Creates a new `PromptModule` with the given questions
    pub fn new<I>(questions: I) -> Self
    where
        I: IntoIterator<IntoIter = Q, Item = Question<'a>>,
    {
        Self {
            answers: Answers::default(),
            questions: questions.into_iter(),
        }
    }

    /// Creates a `PromptModule` with the given questions and answers
    pub fn with_answers(mut self, answers: Answers) -> Self {
        self.answers = answers;
        self
    }

    /// Prompt a single question with the default [`Backend`] and [`EventIterator`].
    ///
    /// This may or may not actually prompt the question based on what `when` and `ask_if_answered`
    /// returns for that particular question.
    #[cfg(any(feature = "crossterm", feature = "termion"))]
    #[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
    pub fn prompt(&mut self) -> crate::Result<Option<&mut Answer>> {
        let stdout = std::io::stdout();
        let mut stdout = ui::backend::get_backend(stdout.lock());

        self.prompt_with(&mut stdout, &mut ui::events::get_events())
    }

    /// Prompt a single question with the given [`Backend`] and [`EventIterator`].
    ///
    /// This may or may not actually prompt the question based on what `when` and `ask_if_answered`
    /// returns for that particular question.
    pub fn prompt_with<B, E>(
        &mut self,
        backend: &mut B,
        events: &mut E,
    ) -> crate::Result<Option<&mut Answer>>
    where
        B: Backend,
        E: EventIterator,
    {
        while let Some(question) = self.questions.next() {
            if let Some((name, answer)) = question.ask(&self.answers, backend, events)? {
                return Ok(Some(self.answers.insert(name, answer)));
            }
        }

        Ok(None)
    }

    /// Prompt all remaining questions with the default [`Backend`] and [`EventIterator`].
    ///
    /// It consumes `self` and returns the answers to all the questions asked.
    #[cfg(any(feature = "crossterm", feature = "termion"))]
    #[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
    pub fn prompt_all(self) -> crate::Result<Answers> {
        let stdout = std::io::stdout();
        let mut stdout = ui::backend::get_backend(stdout.lock());
        let mut events = ui::events::get_events();

        self.prompt_all_with(&mut stdout, &mut events)
    }

    /// Prompt all remaining questions with the given [`Backend`] and [`EventIterator`].
    ///
    /// It consumes `self` and returns the answers to all the questions asked.
    pub fn prompt_all_with<B, E>(
        mut self,
        backend: &mut B,
        events: &mut E,
    ) -> crate::Result<Answers>
    where
        B: Backend,
        E: EventIterator,
    {
        self.answers.reserve(self.questions.size_hint().0);

        while self.prompt_with(backend, events)?.is_some() {}

        Ok(self.answers)
    }

    /// Consumes `self` returning the answers to the previously asked questions.
    pub fn into_answers(self) -> Answers {
        self.answers
    }
}

/// A macro to easily write a [`PromptModule`].
///
/// # Usage
///
/// You can specify questions similar to a `vec![]` of struct instantiations. Each field corresponds
/// to calling the builder method of the same name for the respective question kind.
/// ```
/// # let some_variable = "message";
/// # let when = true;
/// # fn get_default() -> bool { true }
/// use requestty::prompt_module;
///
/// let prompt_module = prompt_module![
///     MultiSelect {
///         // Each field takes a value, which can result in anything that implements `Into`
///         // for the required type
///         name: "name",
///         // The value can be any expression, for example a local variable.
///         message: some_variable,
///         // If the field name and the variable name are the same, this shorthand can be
///         // used.
///         when,
///         // While most values are generic expressions, if a array literal is passed to
///         // choices, some special syntax applies. Also, unlike other fields, 'choices'
///         // will call `choices_with_default` for `MultiSelect` questions only.
///         choices: [
///             // By default array entries are taken as `Choice(_)`s.
///             "Choice 1",
///             // For `MultiSelect` if the word 'default' follows the initial expression, a
///             // default can be set for that choice.
///             "Choice 2" default get_default(),
///             // The word 'separator' or 'sep' can be used to create separators. If no
///             // expression is given along with 'separator', it is taken as a `DefaultSeparator`.
///             separator,
///             // Otherwise if there is an expression, it is taken as a `Separator(_)`,
///             sep "Separator text!",
///         ],
///     },
/// ];
/// ```
///
/// # Inline
///
/// By default, the questions are stored in a [`Vec`]. However, if you wish to store the questions
/// on the stack, prefix the questions with `inline`:
/// ```
/// use requestty::prompt_module;
///
/// let prompt_module = prompt_module![ inline
///     Input {
///         name: "input"
///     },
/// ];
/// ```
///
/// Note that inlining only works for rust version 1.51 onwards. Pre 1.51, a [`Vec`] is still used.
///
/// See also [`questions`].
///
/// [`questions`]: crate::questions
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[macro_export]
macro_rules! prompt_module {
    ($($tt:tt)*) => {
        $crate::PromptModule::new($crate::questions! [ $($tt)* ])
    };
}