requestty/question/
custom_prompt.rs

1use ui::{backend::Backend, events::EventIterator};
2
3use super::{Options, Question, QuestionKind};
4use crate::{Answer, Answers};
5
6/// Prompts are a way to write custom [`Question`]s.
7///
8/// The prompt is given a `message`, the previous [`Answers`] and a [`Backend`] and
9/// [`EventIterator`]. Using these, it is responsible for doing everything from rendering to user
10/// interaction. While no particular look is enforced, it is recommended to keep a similar look to
11/// the rest of the in-built questions.
12///
13/// You can use the `requestty-ui` crate to build the prompts. You can see the implementations of
14/// the in-built questions for examples on how to use it.
15///
16/// See also [`Question::custom`]
17pub trait Prompt: std::fmt::Debug {
18    /// Prompt the user with the given message, [`Answers`], [`Backend`] and [`EventIterator`]
19    fn ask(
20        self,
21        message: String,
22        answers: &Answers,
23        backend: &mut dyn Backend,
24        events: &mut dyn EventIterator,
25    ) -> ui::Result<Option<Answer>>;
26}
27
28/// The same trait as `Prompt`, except it take `&mut self` instead of `self`.
29///
30/// This is required since traits with functions that take `self` are not object safe, and so
31/// implementors of the trait would have to use &mut self even though it will only be called once.
32///
33/// Now instead of QuestionKind::Custom having a `dyn Prompt`, it has a `dyn CustomPromptInteral`, which
34/// is an `Option<T: Prompt>`.
35pub(super) trait CustomPromptInteral: std::fmt::Debug {
36    fn ask(
37        &mut self,
38        message: String,
39        answers: &Answers,
40        backend: &mut dyn Backend,
41        events: &mut dyn EventIterator,
42    ) -> ui::Result<Option<Answer>>;
43}
44
45impl<T: Prompt> CustomPromptInteral for Option<T> {
46    fn ask(
47        &mut self,
48        message: String,
49        answers: &Answers,
50        backend: &mut dyn Backend,
51        events: &mut dyn EventIterator,
52    ) -> ui::Result<Option<Answer>> {
53        self.take()
54            .expect("Prompt::ask called twice")
55            .ask(message, answers, backend, events)
56    }
57}
58
59/// The builder for [custom questions].
60///
61/// See [`Prompt`] for more information on writing custom prompts.
62///
63/// [custom questions]: crate::question::Question::custom
64///
65/// # Examples
66///
67/// ```
68/// use requestty::{prompt, Question};
69///
70/// #[derive(Debug)]
71/// struct MyPrompt { /* ... */ }
72///
73/// # impl MyPrompt {
74/// #     fn new() -> MyPrompt {
75/// #         MyPrompt {}
76/// #     }
77/// # }
78///
79/// impl prompt::Prompt for MyPrompt {
80///     fn ask(
81///         self,
82///         message: String,
83///         answers: &prompt::Answers,
84///         backend: &mut dyn prompt::Backend,
85///         events: &mut dyn prompt::EventIterator,
86///     ) -> requestty::Result<Option<prompt::Answer>> {
87///         // ...
88/// #         todo!()
89///     }
90/// }
91///
92/// let prompt = Question::custom("my-prompt", MyPrompt::new())
93///     .message("Hello from MyPrompt!")
94///     .build();
95/// ```
96#[derive(Debug)]
97pub struct CustomPromptBuilder<'a> {
98    opts: Options<'a>,
99    prompt: Box<dyn CustomPromptInteral + 'a>,
100}
101
102impl<'a> CustomPromptBuilder<'a> {
103    pub(super) fn new(name: String, prompt: Box<dyn CustomPromptInteral + 'a>) -> Self {
104        Self {
105            opts: Options::new(name),
106            prompt,
107        }
108    }
109
110    crate::impl_options_builder! {
111    message
112    /// # Examples
113    ///
114    /// ```
115    /// use requestty::{prompt, Question};
116    ///
117    /// #[derive(Debug)]
118    /// struct MyPrompt { /* ... */ }
119    ///
120    /// # impl MyPrompt {
121    /// #     fn new() -> MyPrompt {
122    /// #         MyPrompt {}
123    /// #     }
124    /// # }
125    ///
126    /// impl prompt::Prompt for MyPrompt {
127    ///     fn ask(
128    ///         self,
129    ///         message: String,
130    ///         answers: &prompt::Answers,
131    ///         backend: &mut dyn prompt::Backend,
132    ///         events: &mut dyn prompt::EventIterator,
133    ///     ) -> requestty::Result<Option<prompt::Answer>> {
134    ///         // ...
135    /// #         todo!()
136    ///     }
137    /// }
138    ///
139    /// let prompt = Question::custom("my-prompt", MyPrompt::new())
140    ///     .message("Hello from MyPrompt!")
141    ///     .build();
142    /// ```
143
144    when
145    /// # Examples
146    ///
147    /// ```
148    /// use requestty::{prompt, Question, Answers};
149    ///
150    /// #[derive(Debug)]
151    /// struct MyPrompt { /* ... */ }
152    ///
153    /// # impl MyPrompt {
154    /// #     fn new() -> MyPrompt {
155    /// #         MyPrompt {}
156    /// #     }
157    /// # }
158    ///
159    /// impl prompt::Prompt for MyPrompt {
160    ///     fn ask(
161    ///         self,
162    ///         message: String,
163    ///         answers: &prompt::Answers,
164    ///         backend: &mut dyn prompt::Backend,
165    ///         events: &mut dyn prompt::EventIterator,
166    ///     ) -> requestty::Result<Option<prompt::Answer>> {
167    ///         // ...
168    /// #         todo!()
169    ///     }
170    /// }
171    ///
172    /// let prompt = Question::custom("my-prompt", MyPrompt::new())
173    ///     .when(|previous_answers: &Answers| match previous_answers.get("use-custom-prompt") {
174    ///         Some(ans) => !ans.as_bool().unwrap(),
175    ///         None => true,
176    ///     })
177    ///     .build();
178    /// ```
179
180    ask_if_answered
181    /// # Examples
182    ///
183    /// ```
184    /// use requestty::{prompt, Question};
185    ///
186    /// #[derive(Debug)]
187    /// struct MyPrompt { /* ... */ }
188    ///
189    /// # impl MyPrompt {
190    /// #     fn new() -> MyPrompt {
191    /// #         MyPrompt {}
192    /// #     }
193    /// # }
194    ///
195    /// impl prompt::Prompt for MyPrompt {
196    ///     fn ask(
197    ///         self,
198    ///         message: String,
199    ///         answers: &prompt::Answers,
200    ///         backend: &mut dyn prompt::Backend,
201    ///         events: &mut dyn prompt::EventIterator,
202    ///     ) -> requestty::Result<Option<prompt::Answer>> {
203    ///         // ...
204    /// #         todo!()
205    ///     }
206    /// }
207    ///
208    /// let prompt = Question::custom("my-prompt", MyPrompt::new())
209    ///     .ask_if_answered(true)
210    ///     .build();
211    /// ```
212    }
213
214    /// Consumes the builder returning a [`Question`]
215    pub fn build(self) -> Question<'a> {
216        Question::new(self.opts, QuestionKind::Custom(self.prompt))
217    }
218}
219
220impl<'a> From<CustomPromptBuilder<'a>> for Question<'a> {
221    /// Consumes the builder returning a [`Question`]
222    fn from(builder: CustomPromptBuilder<'a>) -> Self {
223        builder.build()
224    }
225}