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}