use std::str::FromStr;
use crate::{
config::get_configuration,
error::{InquireError, InquireResult},
formatter::CustomTypeFormatter,
input::Input,
parser::CustomTypeParser,
terminal::get_default_terminal,
ui::{Backend, CustomTypeBackend, Key, RenderConfig},
validator::{CustomTypeValidator, ErrorMessage, Validation},
};
#[derive(Clone)]
pub struct CustomType<'a, T> {
pub message: &'a str,
pub default: Option<T>,
pub placeholder: Option<&'a str>,
pub help_message: Option<&'a str>,
pub formatter: CustomTypeFormatter<'a, T>,
pub default_value_formatter: CustomTypeFormatter<'a, T>,
pub parser: CustomTypeParser<'a, T>,
pub validators: Vec<Box<dyn CustomTypeValidator<T>>>,
pub error_message: String,
pub render_config: RenderConfig,
}
impl<'a, T> CustomType<'a, T>
where
T: Clone,
{
pub const DEFAULT_VALIDATORS: Vec<Box<dyn CustomTypeValidator<T>>> = vec![];
pub fn new(message: &'a str) -> Self
where
T: FromStr + ToString,
{
Self {
message,
default: None,
placeholder: None,
help_message: None,
formatter: &|val| val.to_string(),
default_value_formatter: &|val| val.to_string(),
parser: &|a| a.parse::<T>().map_err(|_| ()),
validators: Self::DEFAULT_VALIDATORS,
error_message: "Invalid input".into(),
render_config: get_configuration(),
}
}
pub fn with_default(mut self, default: T) -> Self {
self.default = Some(default);
self
}
pub fn with_placeholder(mut self, placeholder: &'a str) -> Self {
self.placeholder = Some(placeholder);
self
}
pub fn with_help_message(mut self, message: &'a str) -> Self {
self.help_message = Some(message);
self
}
pub fn with_formatter(mut self, formatter: CustomTypeFormatter<'a, T>) -> Self {
self.formatter = formatter;
self
}
pub fn with_default_value_formatter(mut self, formatter: CustomTypeFormatter<'a, T>) -> Self {
self.default_value_formatter = formatter;
self
}
pub fn with_parser(mut self, parser: CustomTypeParser<'a, T>) -> Self {
self.parser = parser;
self
}
pub fn with_validator<V>(mut self, validator: V) -> Self
where
V: CustomTypeValidator<T> + 'static,
{
if self.validators.capacity() == 0 {
self.validators.reserve(5);
}
self.validators.push(Box::new(validator));
self
}
pub fn with_validators(mut self, validators: &[Box<dyn CustomTypeValidator<T>>]) -> Self {
for validator in validators {
#[allow(clippy::clone_double_ref)]
self.validators.push(validator.clone());
}
self
}
pub fn with_error_message(mut self, error_message: &'a str) -> Self {
self.error_message = String::from(error_message);
self
}
pub fn with_render_config(mut self, render_config: RenderConfig) -> Self {
self.render_config = render_config;
self
}
pub fn prompt_skippable(self) -> InquireResult<Option<T>> {
match self.prompt() {
Ok(answer) => Ok(Some(answer)),
Err(InquireError::OperationCanceled) => Ok(None),
Err(err) => Err(err),
}
}
pub fn prompt(self) -> InquireResult<T> {
let terminal = get_default_terminal()?;
let mut backend = Backend::new(terminal, self.render_config)?;
self.prompt_with_backend(&mut backend)
}
pub(crate) fn prompt_with_backend<B: CustomTypeBackend>(
self,
backend: &mut B,
) -> InquireResult<T> {
CustomTypePrompt::from(self).prompt(backend)
}
}
struct CustomTypePrompt<'a, T> {
message: &'a str,
error: Option<ErrorMessage>,
help_message: Option<&'a str>,
default: Option<T>,
input: Input,
formatter: CustomTypeFormatter<'a, T>,
default_value_formatter: CustomTypeFormatter<'a, T>,
validators: Vec<Box<dyn CustomTypeValidator<T>>>,
parser: CustomTypeParser<'a, T>,
error_message: String,
}
impl<'a, T> From<CustomType<'a, T>> for CustomTypePrompt<'a, T>
where
T: Clone,
{
fn from(co: CustomType<'a, T>) -> Self {
Self {
message: co.message,
error: None,
default: co.default,
help_message: co.help_message,
formatter: co.formatter,
default_value_formatter: co.default_value_formatter,
validators: co.validators,
parser: co.parser,
input: co
.placeholder
.map(|p| Input::new().with_placeholder(p))
.unwrap_or_else(Input::new),
error_message: co.error_message,
}
}
}
impl<'a, T> CustomTypePrompt<'a, T>
where
T: Clone,
{
fn on_change(&mut self, key: Key) {
self.input.handle_key(key);
}
fn validate_current_answer(&self, value: &T) -> InquireResult<Validation> {
for validator in &self.validators {
match validator.validate(value) {
Ok(Validation::Valid) => {}
Ok(Validation::Invalid(msg)) => return Ok(Validation::Invalid(msg)),
Err(err) => return Err(InquireError::Custom(err)),
}
}
Ok(Validation::Valid)
}
fn get_final_answer(&self) -> Result<T, String> {
match &self.default {
Some(val) if self.input.content().is_empty() => return Ok(val.clone()),
_ => {}
}
match (self.parser)(self.input.content()) {
Ok(val) => Ok(val),
Err(_) => Err(self.error_message.clone()),
}
}
fn render<B: CustomTypeBackend>(&mut self, backend: &mut B) -> InquireResult<()> {
let prompt = &self.message;
backend.frame_setup()?;
if let Some(error) = &self.error {
backend.render_error_message(error)?;
}
let default_value_formatter = self.default_value_formatter;
let default_message = self
.default
.as_ref()
.map(|val| default_value_formatter(val.clone()));
backend.render_prompt(prompt, default_message.as_deref(), &self.input)?;
if let Some(message) = self.help_message {
backend.render_help_message(message)?;
}
backend.frame_finish()?;
Ok(())
}
fn prompt<B: CustomTypeBackend>(mut self, backend: &mut B) -> InquireResult<T> {
let final_answer: T;
loop {
self.render(backend)?;
let key = backend.read_key()?;
match key {
Key::Interrupt => interrupt_prompt!(),
Key::Cancel => cancel_prompt!(backend, self.message),
Key::Submit => match self.get_final_answer() {
Ok(answer) => match self.validate_current_answer(&answer)? {
Validation::Valid => {
final_answer = answer;
break;
}
Validation::Invalid(msg) => self.error = Some(msg),
},
Err(message) => self.error = Some(message.into()),
},
key => self.on_change(key),
}
}
let formatted = (self.formatter)(final_answer.clone());
finish_prompt_with_answer!(backend, self.message, &formatted, final_answer);
}
}