#![doc = include_str!("../README.md")]
#![warn(clippy::pedantic)]
#![warn(missing_docs)]
use core::str::FromStr;
use std::fmt::Display;
use std::io;
use std::io::Write;
use yansi::Paint;
pub type QueryResult<T> = io::Result<T>;
fn query_input() -> QueryResult<String> {
let stdin = io::stdin();
let mut buffer = String::new();
stdin.read_line(&mut buffer)?;
Ok(buffer)
}
fn write_prompt(
text: &str,
def: Option<&impl Display>,
prefix: &impl Display,
) -> QueryResult<String> {
let mut stdout = io::stdout();
write!(stdout, "{} ", prefix)?;
if let Some(ref def) = def {
write!(
stdout,
"{} {}{} ",
Paint::green("(Defaulting to:"),
Paint::green(def),
Paint::green(")"),
)?;
}
write!(stdout, "{} ", text)?;
stdout.flush()?;
query_input()
}
pub fn string(string: &str) -> io::Result<String> {
string_with_default(string, Option::<String>::None)
}
pub fn string_with_default(
text: &str,
def: Option<impl Into<String> + Display>,
) -> QueryResult<String> {
let buffer = write_prompt(text, def.as_ref(), &Paint::yellow("::").bold())?;
match def {
Some(def)
if buffer.trim().is_empty() &&
(buffer.len() < 2 || &buffer[..buffer.len() - 1] != " ") =>
{
Ok(def.into())
}
_ => Ok(buffer.trim().to_owned()),
}
}
pub fn parsable_with_default<T: FromStr + Display>(
text: &str,
def: T,
) -> QueryResult<T> {
let buffer = write_prompt(text, Some(&def), &Paint::blue("::").bold())?;
if buffer.trim().is_empty() {
Ok(def)
} else {
match buffer.trim().parse() {
Ok(o) => Ok(o),
Err(_e) => {
println!(
"{}",
Paint::red("Failed to be parse input! Retrying...")
);
parsable_with_default(text, def)
}
}
}
}
pub fn parsable<T>(text: &str) -> QueryResult<T>
where
T: FromStr,
<T as FromStr>::Err: ToString,
{
let buffer =
write_prompt(text, Option::<&u8>::None, &Paint::blue("::").bold())?;
match buffer.trim().parse() {
Ok(o) => Ok(o),
Err(e) => {
println!();
error(
"Failed to be parse input! Retrying...",
Some(&e.to_string()),
);
println!();
parsable(text)
}
}
}
pub fn boolean(text: &str, def: Option<bool>) -> QueryResult<bool> {
let mut stdout = io::stdout();
write!(stdout, "{} ", Paint::magenta("::").bold())?;
write!(stdout, "{} ", text)?;
write!(
stdout,
"{} ",
Paint::green(match def {
Some(true) => "[Y/n]",
Some(false) => "[y/N]",
None => "[y/n]",
}),
)?;
stdout.flush()?;
let buffer = query_input()?;
match (
def,
buffer
.trim()
.chars()
.next()
.and_then(|x| x.to_lowercase().next())
) {
(_, Some('y')) => Ok(true),
(_, Some('n')) => Ok(false),
(Some(def), None) => Ok(def),
_ => {
println!();
error(
"Failed to be parse input! Retrying...",
Some("Use either `y` or `n` as your response"),
);
println!();
boolean(text, def)
}
}
}
pub fn error(string: &str, suggestion: Option<&str>) {
match suggestion {
Some(suggestion) => {
eprintln!("{} {}", Paint::red("Error:").bold(), string);
eprintln!(" {} {}", Paint::cyan("Suggestion:"), suggestion);
}
None => {
eprintln!("{} {}", Paint::red("Error:").bold(), string);
}
}
}
pub fn warning(string: &str, suggestion: Option<&str>) {
match suggestion {
Some(suggestion) => {
eprintln!("{} {}", Paint::yellow("Warning:").bold(), string);
eprintln!(" {} {}", Paint::cyan("Suggestion:"), suggestion);
}
None => {
eprintln!("{} {}", Paint::yellow("Warning:").bold(), string);
}
}
}