may_clack/
multi_input.rs

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