use crate::prompt_config::PromptConfig;
use crate::prompt_error::PromptError;
use std::io::{stdin, stdout, Write};
pub fn chomp(s: &mut String) -> String {
let mut chomped: String = String::new();
if let Some('\n') = s.chars().next_back() {
s.pop();
chomped += "\n";
}
if let Some('\r') = s.chars().next_back() {
s.pop();
chomped += "\r";
}
chomped
}
pub struct Prompt {
config: PromptConfig,
}
impl Prompt {
pub fn new() -> Prompt {
Prompt {
config: PromptConfig::default(),
}
}
pub fn not_blank() -> Prompt {
Prompt {
config: PromptConfig::not_blank(),
}
}
pub fn yn() -> Prompt {
Prompt {
config: PromptConfig::yn(),
}
}
pub fn default(mut self, default: &str) -> Prompt {
self.config.default = Some(String::from(default));
self
}
pub fn choices(mut self, choices: Vec<&str>) -> Self {
let mapped: Vec<String> = choices.iter().map(|&i| String::from(i)).collect();
self.config.choices = Some(mapped);
self
}
pub fn validate(
mut self,
func: impl Fn(String) -> Result<String, PromptError> + 'static,
) -> Prompt {
self.config.validator = Some(Box::new(func));
self
}
pub fn ask(&self, question: &str) -> Result<String, PromptError> {
if let Some(validator) = &self.config.validator {
return self.validate_loop(question, validator);
}
if let Some(choices) = &self.config.choices {
return self.validate_loop(question, |input: String| {
let err_msg = format!("Valid options are: {}", choices.join(", "));
match choices.iter().any(|i| i == &input) {
true => Ok(input),
false => Err(PromptError::ValidateError(err_msg)),
}
});
}
print!("{}", self.format(question));
let mut s: String = self.take_input()?;
if s.is_empty() {
if let Some(default) = &self.config.default {
s = default.to_string();
}
}
Ok(s)
}
fn validate_loop<F>(&self, question: &str, func: F) -> Result<String, PromptError>
where
F: Fn(String) -> Result<String, PromptError>,
{
let mut answer: Option<String> = None;
let mut success = false;
while !success {
print!("{}", &self.format(question));
let res = self.take_input()?;
match func(res) {
Ok(val) => {
answer = Some(val);
success = true;
}
Err(e) => {
println!("{}", e)
}
}
}
match answer {
Some(answer) => Ok(answer),
None => Err(PromptError::InconcievableError()),
}
}
fn take_input(&self) -> Result<String, PromptError> {
let _ = stdout().flush();
let mut s = String::new();
match stdin().read_line(&mut s) {
Ok(_) => chomp(&mut s),
Err(e) => return Err(PromptError::InputError(e)),
};
Ok(s)
}
fn format(&self, question: &str) -> String {
let mut our_question = String::from(question);
let line_end = chomp(&mut our_question);
our_question = String::from(our_question.trim());
if let Some(choices) = &self.config.choices {
our_question = format!("{} [{}]", our_question, choices.join(", "));
} else if let Some(default) = &self.config.default {
our_question = format!("{} [{}]", our_question, default);
}
format!("{}{} ", our_question, line_end)
}
}
#[cfg(test)]
mod tests {
use crate::Prompt;
#[test]
fn format_plain() {
let prompt = Prompt::new();
assert_eq!(prompt.format("Dummy"), "Dummy ");
}
#[test]
fn format_with_default() {
let prompt = Prompt::new().default("def");
assert_eq!(prompt.format("Dummy"), "Dummy [def] ");
}
#[test]
fn format_with_choices() {
let prompt = Prompt::new().choices(["a", "b", "c"].to_vec());
assert_eq!(prompt.format("Dummy"), "Dummy [a, b, c] ");
}
#[test]
fn format_with_choices_and_default() {
let prompt = Prompt::new().choices(["a", "b", "c"].to_vec()).default("a");
assert_eq!(prompt.format("Dummy"), "Dummy [a, b, c] ");
}
}