Skip to main content

may_clack/
multi_input.rs

1//! Multiple text inputs
2
3use super::input::{PlaceholderHighlighter, ValidateFn};
4use crate::{
5	error::ClackError,
6	outro,
7	style::{ansi, chars},
8};
9use crossterm::{QueueableCommand, cursor};
10use owo_colors::OwoColorize;
11use rustyline::Editor;
12use std::{
13	borrow::Cow,
14	error::Error,
15	fmt::Display,
16	io::{Write, stdout},
17	str::FromStr,
18};
19
20/// `MultiInput` struct
21///
22/// # Examples
23///
24/// ```no_run
25/// use may_clack::multi_input;
26/// # use std::borrow::Cow;
27///
28/// # fn main() -> Result<(), may_clack::error::ClackError> {
29/// let answers = multi_input("message")
30///     .validate(|x| {
31///         if x.is_ascii() {
32///             Ok(())
33///         } else {
34///             Err(Cow::Borrowed("only use ascii characters"))
35///         }
36///     })
37///     .interact()?;
38/// println!("answers {:?}", answers);
39/// # Ok(())
40/// # }
41/// ```
42pub struct MultiInput<M: Display> {
43	message: M,
44	initial_value: Option<String>,
45	placeholder: Option<String>,
46	validate: Option<Box<ValidateFn>>,
47	cancel: Option<Box<dyn Fn()>>,
48	min: u16,
49	max: u16,
50}
51
52impl<M: Display> MultiInput<M> {
53	/// Creates a new `MultiInput` struct.
54	///
55	/// Has a shorthand version in [`multi_input()`]
56	///
57	/// # Examples
58	///
59	/// ```no_run
60	/// use may_clack::{multi_input, multi_input::MultiInput};
61	///
62	/// // these two are equivalent
63	/// let question = MultiInput::new("message");
64	/// let question = multi_input("message");
65	/// ```
66	pub fn new(message: M) -> Self {
67		MultiInput {
68			message,
69			validate: None,
70			initial_value: None,
71			placeholder: None,
72			cancel: None,
73			min: 1,
74			max: u16::MAX,
75		}
76	}
77
78	/// Specify the initial value.
79	///
80	/// # Examples
81	///
82	/// ```no_run
83	/// use may_clack::multi_input;
84	///
85	/// # fn main() -> Result<(), may_clack::error::ClackError> {
86	/// let answers = multi_input("message")
87	///     .initial_value("initial_value")
88	///     .interact()?;
89	/// println!("answers {:?}", answers);
90	/// # Ok(())
91	/// # }
92	/// ```
93	pub fn initial_value<S: ToString>(&mut self, initial_value: S) -> &mut Self {
94		self.initial_value = Some(initial_value.to_string());
95		self
96	}
97
98	/// Specify a placeholder.
99	///
100	/// # Examples
101	///
102	/// ```no_run
103	/// use may_clack::multi_input;
104	///
105	/// # fn main() -> Result<(), may_clack::error::ClackError> {
106	/// let answers = multi_input("message")
107	///     .placeholder("placeholder")
108	///     .interact()?;
109	/// println!("answers {:?}", answers);
110	/// # Ok(())
111	/// # }
112	/// ```
113	pub fn placeholder<S: ToString>(&mut self, placeholder: S) -> &mut Self {
114		self.placeholder = Some(placeholder.to_string());
115		self
116	}
117
118	/// Specify the minimum amount of answers.
119	///
120	/// ```no_run
121	/// use may_clack::multi_input;
122	///
123	/// # fn main() -> Result<(), may_clack::error::ClackError> {
124	/// let answers = multi_input("message").min(2).interact()?;
125	/// println!("answers {:?}", answers);
126	/// # Ok(())
127	/// # }
128	/// ```
129	pub fn min(&mut self, min: u16) -> &mut Self {
130		self.min = min;
131		self
132	}
133
134	/// Specify the maximum amount of answers.
135	/// Will automatically submit when that amount is reached.
136	///
137	/// # Examples
138	///
139	/// ```no_run
140	/// use may_clack::multi_input;
141	///
142	/// # fn main() -> Result<(), may_clack::error::ClackError> {
143	/// let answers = multi_input("message").max(4).interact()?;
144	/// println!("answers {:?}", answers);
145	/// # Ok(())
146	/// # }
147	/// ```
148	pub fn max(&mut self, max: u16) -> &mut Self {
149		self.max = max;
150		self
151	}
152
153	/// Specify a validation function.
154	///
155	/// On a successful validation, return a `None` from the closure,
156	/// and on an unsuccessful validation return a `Some<&'static str>` with the error message.
157	///
158	/// # Examples
159	///
160	/// ```no_run
161	/// use may_clack::multi_input;
162	/// # use std::borrow::Cow;
163	///
164	/// # fn main() -> Result<(), may_clack::error::ClackError> {
165	/// let answers = multi_input("message")
166	///     .validate(|x| {
167	///         if x.find(char::is_whitespace).is_some() {
168	///             Err(Cow::Borrowed("whitespace is disallowed"))
169	///         } else {
170	///             Ok(())
171	///         }
172	///     })
173	///     .interact()?;
174	/// println!("answers {:?}", answers);
175	/// # Ok(())
176	/// # }
177	/// ```
178	pub fn validate<F>(&mut self, validate: F) -> &mut Self
179	where
180		F: Fn(&str) -> Result<(), Cow<'static, str>> + 'static,
181	{
182		let validate = Box::new(validate);
183		self.validate = Some(validate);
184		self
185	}
186
187	fn do_validate(&self, input: &str) -> Result<(), Cow<'static, str>> {
188		if let Some(validate) = self.validate.as_deref() {
189			validate(input)
190		} else {
191			Ok(())
192		}
193	}
194
195	/// Specify function to call on cancel.
196	///
197	/// # Examples
198	///
199	/// ```no_run
200	/// use may_clack::{multi_input, cancel};
201	///
202	/// # fn main() -> Result<(), may_clack::error::ClackError> {
203	/// let answers = multi_input("message").cancel(do_cancel).interact()?;
204	/// println!("answers {:?}", answers);
205	/// # Ok(())
206	/// # }
207	///
208	/// fn do_cancel() {
209	///     cancel!("operation cancelled");
210	///     panic!("operation cancelled");
211	/// }
212	pub fn cancel<F>(&mut self, cancel: F) -> &mut Self
213	where
214		F: Fn() + 'static,
215	{
216		let cancel = Box::new(cancel);
217		self.cancel = Some(cancel);
218		self
219	}
220
221	fn interact_once<T: FromStr>(
222		&self,
223		enforce_non_empty: bool,
224		amt: u16,
225	) -> Result<Option<T>, ClackError>
226	where
227		T::Err: Error,
228	{
229		let prompt = format!("{}  ", *chars::BAR);
230		let mut editor = Editor::new()?;
231
232		let highlighter = PlaceholderHighlighter::new(self.placeholder.as_deref());
233		editor.set_helper(Some(highlighter));
234
235		let mut initial_value = self.initial_value.as_deref().map(Cow::Borrowed);
236		loop {
237			let line = if let Some(ref init) = initial_value {
238				editor.readline_with_initial(&prompt, (init, ""))
239			} else {
240				editor.readline(&prompt)
241			};
242
243			// todo this looks refactor-able
244			if let Ok(value) = line {
245				if value.is_empty() {
246					if enforce_non_empty {
247						initial_value = None;
248
249						if let Some(helper) = editor.helper_mut() {
250							helper.is_val = true;
251						}
252
253						let text = format!("minimum {}", self.min);
254						self.w_val(&text, amt);
255					} else {
256						break Ok(None);
257					}
258				} else if let Err(text) = self.do_validate(&value) {
259					initial_value = Some(Cow::Owned(value));
260
261					if let Some(helper) = editor.helper_mut() {
262						helper.is_val = true;
263					}
264
265					self.w_val(&text, amt);
266				} else {
267					match value.parse::<T>() {
268						Ok(value) => break Ok(Some(value)),
269						Err(err) => {
270							initial_value = Some(Cow::Owned(value));
271
272							if let Some(helper) = editor.helper_mut() {
273								helper.is_val = true;
274							}
275
276							self.w_val(&err.to_string(), amt);
277						}
278					}
279					// break Ok(Some(value));
280				}
281			} else {
282				break Err(ClackError::Cancelled);
283			}
284		}
285	}
286
287	/// Like [`MultiInput::interact()`], but parses the value before returning.
288	///
289	/// # Examples
290	///
291	/// ```no_run
292	/// use may_clack::multi_input;
293	///
294	/// # fn main() -> Result<(), may_clack::error::ClackError> {
295	/// let answers: Vec<i32> = multi_input("message").min(2).parse::<i32>()?;
296	/// println!("answers {:?}", answers);
297	///
298	/// # Ok(())
299	/// # }
300	/// ```
301	pub fn parse<T: FromStr + Display>(&self) -> Result<Vec<T>, ClackError>
302	where
303		T::Err: Error,
304	{
305		self.w_init();
306
307		let mut v = vec![];
308		loop {
309			let amt = v.len() as u16;
310
311			let enforce_non_empty = amt < self.min;
312			let once = self.interact_once::<T>(enforce_non_empty, amt);
313
314			match once {
315				Ok(Some(value)) => {
316					self.w_line(&value, amt);
317					v.push(value);
318
319					if v.len() as u16 == self.max {
320						println!();
321						self.w_out(&v);
322						break;
323					}
324				}
325				Ok(None) => {
326					self.w_out(&v);
327					break;
328				}
329				Err(ClackError::Cancelled) => {
330					self.w_cancel(v.len());
331					if let Some(cancel) = self.cancel.as_deref() {
332						cancel();
333					} else {
334						outro!();
335					}
336
337					return Err(ClackError::Cancelled);
338				}
339				Err(err) => return Err(err),
340			}
341		}
342
343		Ok(v)
344	}
345
346	/// Waits for the user to submit a line of text.
347	///
348	/// Returns [`None`] on an empty line and [`Some::<String>`] otherwise.
349	///
350	/// # Examples
351	///
352	/// ```no_run
353	/// use may_clack::multi_input;
354	/// # use std::borrow::Cow;
355	///
356	/// # fn main() -> Result<(), may_clack::error::ClackError> {
357	/// let answers = multi_input("message")
358	///     .validate(|x| {
359	///         if x.contains('\0') {
360	///             Err(Cow::Borrowed("contains nul byte"))
361	///         } else {
362	///             Ok(())
363	///         }
364	///     })
365	///     .interact()?;
366	/// println!("answers {:?}", answers);
367	/// # Ok(())
368	/// # }
369	/// ```
370	pub fn interact(&self) -> Result<Vec<String>, ClackError> {
371		self.w_init();
372
373		let mut v = vec![];
374		loop {
375			let amt = v.len() as u16;
376
377			let enforce_non_empty = amt < self.min;
378			let once = self.interact_once::<String>(enforce_non_empty, amt);
379
380			match once {
381				Ok(Some(value)) => {
382					self.w_line(&value, amt);
383					v.push(value);
384
385					if v.len() as u16 == self.max {
386						println!();
387						self.w_out(&v);
388						break;
389					}
390				}
391				Ok(None) => {
392					self.w_out(&v);
393					break;
394				}
395				Err(ClackError::Cancelled) => {
396					self.w_cancel(v.len());
397					if let Some(cancel) = self.cancel.as_deref() {
398						cancel();
399					} else {
400						outro!();
401					}
402
403					return Err(ClackError::Cancelled);
404				}
405				Err(err) => return Err(err),
406			}
407		}
408
409		Ok(v)
410	}
411}
412
413impl<M: Display> MultiInput<M> {
414	fn w_init(&self) {
415		let mut stdout = stdout();
416
417		println!("{}", *chars::BAR);
418		println!("{}  {}", (*chars::STEP_ACTIVE).cyan(), self.message);
419		println!("{}", (*chars::BAR).cyan());
420		print!("{}", (*chars::BAR_END).cyan());
421
422		let _ = stdout.queue(cursor::MoveToPreviousLine(1));
423		let _ = stdout.flush();
424
425		print!("{}  ", (*chars::BAR).cyan());
426		let _ = stdout.flush();
427	}
428
429	fn w_line<V: Display>(&self, value: V, amt: u16) {
430		let mut stdout = stdout();
431		let _ = stdout.queue(cursor::MoveToPreviousLine(amt + 2));
432		let _ = stdout.flush();
433
434		println!("{}  {}", (*chars::STEP_ACTIVE).cyan(), self.message);
435
436		for _ in 0..amt {
437			println!("{}", (*chars::BAR).cyan());
438		}
439
440		println!("{}  {}", (*chars::BAR).cyan(), value.dimmed());
441		println!("{}", (*chars::BAR).cyan());
442
443		print!("{}", ansi::CLEAR_LINE);
444		print!("{}", (*chars::BAR_END).cyan());
445
446		let _ = stdout.queue(cursor::MoveToPreviousLine(1));
447		let _ = stdout.flush();
448	}
449
450	fn w_val(&self, text: &str, amt: u16) {
451		let mut stdout = stdout();
452		let _ = stdout.queue(cursor::MoveToPreviousLine(amt + 2));
453		let _ = stdout.flush();
454
455		println!("{}  {}", (*chars::STEP_ERROR).yellow(), self.message);
456
457		for _ in 0..=amt {
458			println!("{}", (*chars::BAR).yellow());
459		}
460
461		print!("{}", ansi::CLEAR_LINE);
462		print!("{}  {}", (*chars::BAR_END).yellow(), text.yellow());
463
464		let _ = stdout.queue(cursor::MoveToPreviousLine(1));
465		let _ = stdout.flush();
466	}
467
468	fn w_out<V: Display>(&self, values: &[V]) {
469		let amt = values.len();
470
471		let mut stdout = stdout();
472		let _ = stdout.queue(cursor::MoveToPreviousLine(amt as u16 + 2));
473		let _ = stdout.flush();
474
475		println!("{}  {}", (*chars::STEP_SUBMIT).green(), self.message);
476
477		if amt == 0 {
478			println!("{}", *chars::BAR);
479		}
480
481		for val in values {
482			println!("{}  {}", *chars::BAR, val.dimmed());
483		}
484
485		println!("{}", ansi::CLEAR_LINE);
486		println!("{}", ansi::CLEAR_LINE);
487
488		let _ = stdout.queue(cursor::MoveToPreviousLine(2));
489		let _ = stdout.flush();
490	}
491
492	fn w_cancel(&self, amt: usize) {
493		let mut stdout = stdout();
494		let _ = stdout.queue(cursor::MoveToPreviousLine(1));
495		let _ = stdout.flush();
496
497		print!("{}", ansi::CLEAR_LINE);
498		println!("{}  {}", *chars::BAR, "cancelled".strikethrough().dimmed());
499
500		print!("{}", ansi::CLEAR_LINE);
501
502		let _ = stdout.queue(cursor::MoveToPreviousLine(amt as u16 + 2));
503		let _ = stdout.flush();
504
505		println!("{}  {}", (*chars::STEP_CANCEL).red(), self.message);
506
507		for _ in 0..amt {
508			println!("{}", *chars::BAR);
509		}
510
511		let _ = stdout.queue(cursor::MoveToNextLine(1));
512		let _ = stdout.flush();
513	}
514}
515
516/// Shorthand for [`MultiInput::new()`]
517pub fn multi_input<M: Display>(message: M) -> MultiInput<M> {
518	MultiInput::new(message)
519}