use std::fmt::{self, Debug, Display};
use std::io::{self, Write};
use std::str::FromStr;
struct Validator<T> {
raw: Box<dyn Fn(&T) -> bool + 'static>,
}
pub struct Input<T> {
prompt: Option<String>,
prefix: Option<String>,
suffix: Option<String>,
default: Option<T>,
validator: Option<Validator<T>>,
}
impl<T> Validator<T> {
fn new<F>(raw: F) -> Self
where
F: Fn(&T) -> bool + 'static,
{
Self { raw: Box::new(raw) }
}
fn run(&self, input: &T) -> bool {
(self.raw)(input)
}
}
impl<T: Debug> Debug for Input<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Input")
.field("prefix", &self.prefix)
.field("prompt", &self.prompt)
.field("suffix", &self.suffix)
.field("default", &self.default)
.finish() }
}
impl<T> Default for Input<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Input<T> {
pub fn new() -> Self {
Self {
prefix: None,
prompt: None,
suffix: None,
default: None,
validator: None,
}
}
pub fn prompt<S: Into<String>>(mut self, prompt: S) -> Self {
self.prompt = Some(prompt.into());
self
}
pub fn prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn suffix<S: Into<String>>(mut self, suffix: S) -> Self {
self.suffix = Some(suffix.into());
self
}
pub fn default(mut self, default: T) -> Self {
self.default = Some(default);
self
}
pub fn matches<F>(mut self, matches: F) -> Self
where
F: Fn(&T) -> bool + 'static,
{
self.validator = Some(Validator::new(matches));
self
}
}
fn read_line(prompt: &Option<String>) -> io::Result<String> {
if let Some(prompt) = prompt {
let mut stdout = io::stdout();
stdout.write_all(prompt.as_bytes())?;
stdout.flush()?;
}
let mut result = String::new();
io::stdin().read_line(&mut result)?;
Ok(result)
}
impl<T> Input<T>
where
T: FromStr,
<T as FromStr>::Err: Display,
{
fn try_get_with<F>(self, read_line: F) -> io::Result<T>
where
F: Fn(&Option<String>) -> io::Result<String>,
{
let Self {
prompt,
prefix,
suffix,
default,
validator,
} = self;
let prompt = prompt.map(move |prompt| {
let mut p = String::new();
if let Some(prefix) = prefix {
p.push_str(&prefix);
}
p.push_str(&prompt);
if let Some(suffix) = suffix {
p.push_str(&suffix);
}
p
});
Ok(loop {
match read_line(&prompt)?.trim() {
"" => {
if let Some(default) = default {
break default;
} else {
continue;
}
}
raw => match raw.parse() {
Ok(result) => {
if let Some(validator) = &validator {
if !validator.run(&result) {
println!("Error: invalid input");
continue;
}
}
break result;
}
Err(err) => {
println!("Error: {}", err);
continue;
}
},
}
})
}
#[inline]
fn try_get(self) -> io::Result<T> {
self.try_get_with(read_line)
}
pub fn get(self) -> T {
self.try_get().unwrap()
}
pub fn map<F, U>(self, map: F) -> U
where
F: Fn(T) -> U,
{
map(self.get())
}
}
pub fn input<T>() -> Input<T> {
Input::new()
}
pub fn prompt<S, T>(text: S) -> Input<T>
where
S: Into<String>,
{
Input::new().prompt(text)
}
pub fn confirm<S: Into<String>>(text: S) -> bool {
prompt(text)
.suffix(" [y/N] ")
.default("n".to_string())
.matches(|s| matches!(&*s.trim().to_lowercase(), "n" | "no" | "y" | "yes"))
.map(|s| matches!(&*s.to_lowercase(), "y" | "yes"))
}