#![deny(clippy::pedantic, missing_docs)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
mod core;
pub mod prelude;
pub mod shortcut;
mod test_generators;
#[cfg(test)]
mod tests;
use crate::{core::read_input, test_generators::InsideFunc};
use std::{cmp::PartialOrd, io, rc::Rc, str::FromStr, string::ToString};
const DEFAULT_ERR: &str = "That value does not pass. Please try again";
pub trait InputBuild<T: FromStr> {
fn msg(self, msg: impl ToString) -> Self;
fn repeat_msg(self, msg: impl ToString) -> Self;
fn err(self, err: impl ToString) -> Self;
fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self;
fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
where
F: Fn(&T) -> bool + 'static;
fn clear_tests(self) -> Self;
fn err_match<F>(self, err_match: F) -> Self
where
F: Fn(&T::Err) -> Option<String> + 'static;
fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self;
fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self;
fn toggle_msg_repeat(self) -> Self;
}
pub trait InputConstraints<T>: InputBuild<T>
where
T: FromStr + PartialOrd + 'static,
Self: Sized,
{
fn min(self, min: T) -> Self {
self.inside(min..)
}
fn min_err(self, min: T, err: impl ToString) -> Self {
self.inside_err(min.., err)
}
fn max(self, max: T) -> Self {
self.inside(..=max)
}
fn max_err(self, max: T, err: impl ToString) -> Self {
self.inside_err(..=max, err)
}
fn min_max(self, min: T, max: T) -> Self {
self.inside(min..=max)
}
fn min_max_err(self, min: T, max: T, err: impl ToString) -> Self {
self.inside_err(min..=max, err)
}
fn not(self, this: T) -> Self {
self.add_test(move |x: &T| *x != this)
}
fn not_err(self, this: T, err: impl ToString) -> Self {
self.add_err_test(move |x: &T| *x != this, err)
}
}
#[derive(Clone)]
pub(crate) struct Prompt {
pub msg: String,
pub repeat: bool,
}
#[derive(Clone)]
pub(crate) struct Test<T> {
pub func: Rc<dyn Fn(&T) -> bool>,
pub err: Option<String>,
}
pub struct InputBuilder<T: FromStr> {
msg: Prompt,
err: String,
tests: Vec<Test<T>>,
err_match: Rc<dyn Fn(&T::Err) -> Option<String>>,
}
impl<T: FromStr> InputBuilder<T> {
pub fn new() -> Self {
Self {
msg: Prompt {
msg: String::new(),
repeat: false,
},
err: DEFAULT_ERR.to_string(),
tests: Vec::new(),
err_match: Rc::new(|_| None),
}
}
pub fn get(&self) -> T {
self.try_get().expect("Failed to read line")
}
pub fn try_get(&self) -> io::Result<T> {
read_input::<T>(&self.msg, &self.err, None, &self.tests, &*self.err_match)
}
pub fn default(self, default: T) -> InputBuilderOnce<T> {
InputBuilderOnce {
builder: self,
default: Some(default),
}
}
fn test_err_opt(mut self, func: Rc<dyn Fn(&T) -> bool>, err: Option<String>) -> Self {
self.tests.push(Test { func, err });
self
}
}
impl<T: FromStr> InputBuild<T> for InputBuilder<T> {
fn msg(mut self, msg: impl ToString) -> Self {
self.msg = Prompt {
msg: msg.to_string(),
repeat: false,
};
self
}
fn repeat_msg(mut self, msg: impl ToString) -> Self {
self.msg = Prompt {
msg: msg.to_string(),
repeat: true,
};
self
}
fn err(mut self, err: impl ToString) -> Self {
self.err = err.to_string();
self
}
fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self {
self.test_err_opt(Rc::new(test), None)
}
fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
where
F: Fn(&T) -> bool + 'static,
{
self.test_err_opt(Rc::new(test), Some(err.to_string()))
}
fn clear_tests(mut self) -> Self {
self.tests = Vec::new();
self
}
fn err_match<F>(mut self, err_match: F) -> Self
where
F: Fn(&T::Err) -> Option<String> + 'static,
{
self.err_match = Rc::new(err_match);
self
}
fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self {
self.test_err_opt(constraint.contains_func(), None)
}
fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self {
self.test_err_opt(constraint.contains_func(), Some(err.to_string()))
}
fn toggle_msg_repeat(mut self) -> Self {
self.msg.repeat = !self.msg.repeat;
self
}
}
impl<T: FromStr + PartialOrd + 'static> InputConstraints<T> for InputBuilder<T> {}
impl<T: FromStr> Default for InputBuilder<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: FromStr + Clone> Clone for InputBuilder<T> {
fn clone(&self) -> Self {
Self {
msg: self.msg.clone(),
err: self.err.clone(),
tests: self.tests.clone(),
err_match: self.err_match.clone(),
}
}
}
pub struct InputBuilderOnce<T: FromStr> {
builder: InputBuilder<T>,
default: Option<T>,
}
impl<T: FromStr> InputBuilderOnce<T> {
pub fn get(self) -> T {
self.try_get().expect("Failed to read line")
}
pub fn try_get(self) -> io::Result<T> {
read_input::<T>(
&self.builder.msg,
&self.builder.err,
self.default,
&self.builder.tests,
&*self.builder.err_match,
)
}
fn internal<F>(self, with: F) -> Self
where
F: FnOnce(InputBuilder<T>) -> InputBuilder<T>,
{
Self {
builder: with(self.builder),
..self
}
}
}
impl<T: FromStr> InputBuild<T> for InputBuilderOnce<T> {
fn msg(self, msg: impl ToString) -> Self {
self.internal(|x| x.msg(msg))
}
fn repeat_msg(self, msg: impl ToString) -> Self {
self.internal(|x| x.repeat_msg(msg))
}
fn err(self, err: impl ToString) -> Self {
self.internal(|x| x.err(err))
}
fn add_test<F: Fn(&T) -> bool + 'static>(self, test: F) -> Self {
self.internal(|x| x.add_test(test))
}
fn add_err_test<F>(self, test: F, err: impl ToString) -> Self
where
F: Fn(&T) -> bool + 'static,
{
self.internal(|x| x.add_err_test(test, err))
}
fn clear_tests(self) -> Self {
self.internal(InputBuild::clear_tests)
}
fn err_match<F>(self, err_match: F) -> Self
where
F: Fn(&T::Err) -> Option<String> + 'static,
{
self.internal(|x| x.err_match(err_match))
}
fn inside<U: InsideFunc<T>>(self, constraint: U) -> Self {
self.internal(|x| x.inside(constraint))
}
fn inside_err<U: InsideFunc<T>>(self, constraint: U, err: impl ToString) -> Self {
self.internal(|x| x.inside_err(constraint, err))
}
fn toggle_msg_repeat(self) -> Self {
self.internal(InputBuild::toggle_msg_repeat)
}
}
impl<T: FromStr + PartialOrd + 'static> InputConstraints<T> for InputBuilderOnce<T> {}
impl<T> Clone for InputBuilderOnce<T>
where
T: Clone + FromStr,
{
fn clone(&self) -> Self {
Self {
default: self.default.clone(),
builder: self.builder.clone(),
}
}
}