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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//! User input
use std::marker::PhantomData;
use crate::{
	Buffer,
	Paint,
	Conciliator,
	List,
	Print,
	Pushable
};

/// Requesting user input
///
/// Used by [`Claw::input`](crate::Claw::input), where:
/// - [`print`](Self::print) is called *once* at the start of the input procedure to establish the context of the input being requested,
/// - [`prompt`](Self::prompt) is called to prompt the user for input,
/// - then, a line is read from the standard input and [`trim`](str::trim)med before being passed to
/// - [`validate`](Self::validate), which (parses and) validates the input. If it returns
///     - `Some`, the input procedure ends successfully.
///     - `None`, the user will be [`prompt`](Self::prompt)ed again (and again) until [`validate`](Self::validate) returns [`Some`].
pub trait Input {
	/// Type being requested from the user (after parsing)
	type T;
	/// Make the request
	///
	/// Provide a concise question or request, ideally indicating the expected format.
	/// In general, do not add a trailing newline.
	///
	/// This will be called at least once, and then again for every time [`validate`](Self::validate) returns `None`.
	///
	/// For example: `Are you sure you want to continue? [y/N]: `
	fn prompt(&self, buffer: &mut Buffer);
	/// Parse & validate the user input
	///
	/// Returning `Some(T)` indicates success: the input procedure ends and `T` is returned.
	/// Otherwise, the user will be [`prompt`](Self::prompt)ed to try again.
	fn validate(&self, user_input: &str) -> Option<Self::T>;
	/// Establish the context of the request (optional, the provided method does nothing)
	///
	/// For example: to request a selection to be made from a [`List`] of items, this function is implemented and [`Print`]s the [`List`].
	/// Then [`prompt`](Self::prompt) only specifies the range of valid indices.
	///
	/// This differs from the signature of [`Print`] in that it takes `&mut self` instead `self` by value, because the [`Input`] implementor will still be needed to [`prompt`](Self::prompt) and [`validate`](Self::validate).
	/// Because of this, implementors of this that are wrappers around [`Print`] implementors may chose to use the `if let Some(p) = self.p.take() { p.print(con) }` pattern.
	fn print<C: Conciliator + ?Sized>(&mut self, con: &C) {
		let _ = con;
	}
}

/// Boolean [`Input`], e.g. `Are you sure? [y/N]`
pub struct Confirm<Q, M> {
	question: Q,
	marker: PhantomData<M>,
	default: bool
}

/// Ask the user to pick something from a list by index
pub struct Select<Q, M, L> {
	list: Option<L>,
	question: Q,
	marker: PhantomData<M>,
	len: usize
}


/// Ask whether to "Abort, Retry, or Continue"
///
/// Whichever variant is used with [`Claw::input`](crate::Claw::input) is the default (returned if the user presses enter without making an explicit selection).
///
/// There's no "behavior" inherent to the variants, it's up to you how to implement that.
///
/// For example:
/// ```
/// use conciliator::{Conciliator, input::AbortRetryContinue};
/// let con = conciliator::init();
/// loop {
///     match con.input(AbortRetryContinue::Retry) {
///         AbortRetryContinue::Abort => break Err("Aborted!"),
///         AbortRetryContinue::Retry => break Ok("Retry"),
///         AbortRetryContinue::Continue => continue
///     };
/// };
/// ```
/// Asks:
/// ```text
/// Abort, Retry, or Continue? [a/R/c]:
/// ```
#[derive(Clone, Copy)]
pub enum AbortRetryContinue {
	/// **A**bort
	Abort,
	/// **R**etry
	Retry,
	/// **C**ontinue
	Continue
}

/*
 *	ABORT RETRY CONTINUE
 */

impl Input for AbortRetryContinue {
	type T = Self;
	fn prompt(&self, buffer: &mut Buffer) {
		buffer
			.push_bold("A")
			.push_plain("bort, ")
			.push_bold("R")
			.push_plain("etry, or ")
			.push_bold("C")
			.push_plain("ontinue?");

		match *self {
			Self::Abort => buffer.push_plain(" [A/r/c]: "),
			Self::Retry => buffer.push_plain(" [a/R/c]: "),
			Self::Continue => buffer.push_plain(" [a/r/C]: ")
		};
	}
	fn validate(&self, user_input: &str) -> Option<Self::T> {
		match user_input {
			"" => Some(*self),
			"a" | "A" => Some(Self::Abort),
			"r" | "R" => Some(Self::Retry),
			"c" | "C" => Some(Self::Continue),
			_ => None
		}
	}
}


/*
 *	STRING INPUT
 */

/// Basic [`String`] input, no validation
impl<'s> Input for &'s str {
	type T = String;
	fn prompt(&self, buffer: &mut Buffer) {
		buffer
			.push_plain(self)
			.push_plain(": ");
	}
	fn validate(&self, user_input: &str) -> Option<String> {
		Some(user_input.to_owned())
	}
}

/*
 *	CONFIRM
 */

impl<Q, M> Confirm<Q, M> {
	/// Ask a yes or no question
	pub fn new(default: bool, question: Q) -> Self {
		Self {question, marker: PhantomData, default}
	}
	/// Return `false` for empty input `[y/N]`
	pub fn default_no(question: Q) -> Self {
		Self {question, marker: PhantomData, default: false}
	}
	/// Return `true` for empty input `[Y/n]`
	pub fn default_yes(question: Q) -> Self {
		Self {question, marker: PhantomData, default: true}
	}
}

impl<Q, M> Input for Confirm<Q, M>
	where for<'a> &'a Q: Pushable<M>
{
	type T = bool;
	fn prompt(&self, buffer: &mut Buffer) {
		buffer
			.push(&self.question)
			.push_plain(if self.default {" [Y/n]: "} else {" [y/N]: "});
	}
	fn validate(&self, user_input: &str) -> Option<bool> {
		match user_input {
			"" => Some(self.default),
			"y" | "Y" => Some(true),
			"n" | "N" => Some(false),
			_ => None
		}
	}
}

/*
 *	SELECT
 */

impl<Q, M, H, I, T, F, P> Select<Q, M, List<H, I, F, P>>
	where
		I: ExactSizeIterator<Item = T>,
		List<H, I, F, P>: Print
{
	/// Create a new [`Select`] from a [`List`]
	pub fn new(list: List<H, I, F, P>, question: Q) -> Self {
		Self {
			len: list.len(),
			list: Some(list),
			marker: PhantomData,
			question
		}
	}
}

impl<Q, M, H, I, F, P> Input for Select<Q, M, List<H, I, F, P>>
	where List<H, I, F, P>: Print,
		Q: Pushable<M>
{
	type T = usize;
	fn prompt(&self, buffer: &mut Buffer) {
		self.question.push_into(buffer);
		buffer
			.push_plain(" [1 - ")
			.push_plain(&self.len)
			.push_plain("]: ");
	}
	fn validate(&self, user_input: &str) -> Option<usize> {
		user_input
			.parse()
			.ok()
			.filter(|&i| i > 0)
			.map(|i: usize| i - 1)
			.filter(|i| (0..self.len).contains(i))
	}
	fn print<C: Conciliator + ?Sized>(&mut self, con: &C) {
		if let Some(list) = self.list.take() {
			list.print(con);
		}
	}
}