use crate::elements::*;
use std::io::{self, BufRead, Write};
#[derive(Debug, Clone, PartialEq)]
pub enum PromptValue {
String(String),
Bool(bool),
}
pub struct Question {
pub name: String,
pub type_name: String,
pub message: String,
pub initial_text: Option<String>,
pub initial_bool: Option<bool>,
pub choices: Option<Vec<Choice>>,
pub hint: Option<String>,
}
impl Default for Question {
fn default() -> Self {
Self {
name: String::new(),
type_name: String::new(),
message: String::new(),
initial_text: None,
initial_bool: None,
choices: None,
hint: None,
}
}
}
#[inline]
pub fn run_prompt<R: BufRead, W: Write>(
q: &Question,
stdin: &mut R,
stdout: &mut W,
) -> io::Result<Option<PromptValue>> {
match q.type_name.as_str() {
"text" => {
let opts = TextPromptOptions {
message: q.message.clone(),
initial: q.initial_text.clone(),
};
run_text(&opts, stdin, stdout).map(|s| Some(PromptValue::String(s)))
}
"confirm" => {
let opts = ConfirmPromptOptions {
message: q.message.clone(),
initial: q.initial_bool.unwrap_or(false),
..Default::default()
};
run_confirm(&opts, stdin, stdout).map(|b| Some(PromptValue::Bool(b)))
}
"select" => {
let choices = q.choices.clone().unwrap_or_default();
let opts = SelectPromptOptions {
message: q.message.clone(),
choices,
initial: None,
hint: q.hint.clone(),
};
run_select(&opts, stdin, stdout).map(|s| Some(PromptValue::String(s)))
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("prompt type '{}' is not defined", q.type_name),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn question_default_values() {
let q = Question::default();
assert!(q.name.is_empty());
assert!(q.type_name.is_empty());
assert!(q.message.is_empty());
assert!(q.initial_text.is_none());
assert!(q.initial_bool.is_none());
assert!(q.choices.is_none());
assert!(q.hint.is_none());
}
#[test]
fn run_prompt_text() {
let q = Question {
name: "name".into(),
type_name: "text".into(),
message: "Your name?".into(),
initial_text: Some("default".into()),
..Default::default()
};
let mut stdin = Cursor::new(b"Alice\n");
let mut stdout = Vec::new();
let out = run_prompt(&q, &mut stdin, &mut stdout);
assert!(out.is_ok());
let val = out.unwrap();
assert!(matches!(val, Some(PromptValue::String(s)) if s == "Alice"));
}
#[test]
fn run_prompt_text_empty_uses_initial() {
let q = Question {
name: "x".into(),
type_name: "text".into(),
message: "Msg".into(),
initial_text: Some("init".into()),
..Default::default()
};
let mut stdin = Cursor::new(b"\n");
let mut stdout = Vec::new();
let out = run_prompt(&q, &mut stdin, &mut stdout);
assert!(out.is_ok());
let val = out.unwrap();
assert!(matches!(val, Some(PromptValue::String(s)) if s == "init"));
}
#[test]
fn run_prompt_confirm_yes() {
let q = Question {
name: "ok".into(),
type_name: "confirm".into(),
message: "Continue?".into(),
initial_bool: Some(false),
..Default::default()
};
let mut stdin = Cursor::new(b"y\n");
let mut stdout = Vec::new();
let out = run_prompt(&q, &mut stdin, &mut stdout);
assert!(out.is_ok());
assert!(matches!(out.unwrap(), Some(PromptValue::Bool(true))));
}
#[test]
fn run_prompt_confirm_no() {
let q = Question {
name: "ok".into(),
type_name: "confirm".into(),
message: "Continue?".into(),
initial_bool: Some(true),
..Default::default()
};
let mut stdin = Cursor::new(b"n\n");
let mut stdout = Vec::new();
let out = run_prompt(&q, &mut stdin, &mut stdout);
assert!(out.is_ok());
assert!(matches!(out.unwrap(), Some(PromptValue::Bool(false))));
}
#[test]
fn run_prompt_unknown_type_err() {
let q = Question {
name: "x".into(),
type_name: "unknown_type".into(),
message: "Msg".into(),
..Default::default()
};
let mut stdin = Cursor::new(b"");
let mut stdout = Vec::new();
let out = run_prompt(&q, &mut stdin, &mut stdout);
assert!(out.is_err());
}
}