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)* ]) }; }