use crate::util::style;
use colour::write_bold;
use std::io::{self, BufRead, Write};
pub struct TextPromptOptions {
pub message: String,
pub initial: Option<String>,
}
impl Default for TextPromptOptions {
fn default() -> Self {
Self {
message: String::new(),
initial: None,
}
}
}
pub fn run_text<R: BufRead, W: Write>(
opts: &TextPromptOptions,
stdin: &mut R,
stdout: &mut W,
) -> io::Result<String> {
let initial = opts.initial.as_deref().unwrap_or("");
let mut output = Vec::with_capacity(opts.message.len() + 32);
write_bold!(&mut output, "{}", opts.message).ok();
let msg_styled = String::from_utf8_lossy(&output).into_owned();
let delim = style::delimiter(false);
let prompt_line = format!("{} {} ", msg_styled, delim);
write!(stdout, "{}", prompt_line)?;
stdout.flush()?;
let mut line = String::new();
stdin.read_line(&mut line)?;
let value = line.trim().to_string();
let value = if value.is_empty() {
initial.to_string()
} else {
value
};
stdout.flush()?;
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn text_prompt_options_default() {
let opts = TextPromptOptions::default();
assert!(opts.message.is_empty());
assert!(opts.initial.is_none());
}
#[test]
fn run_text_returns_entered_value() {
let opts = TextPromptOptions {
message: "Name?".into(),
initial: None,
};
let mut stdin = Cursor::new(b"Bob\n");
let mut stdout = Vec::new();
let r = run_text(&opts, &mut stdin, &mut stdout);
assert!(r.is_ok());
assert_eq!(r.unwrap(), "Bob");
}
#[test]
fn run_text_empty_uses_initial() {
let opts = TextPromptOptions {
message: "Name?".into(),
initial: Some("default".into()),
};
let mut stdin = Cursor::new(b"\n");
let mut stdout = Vec::new();
let r = run_text(&opts, &mut stdin, &mut stdout);
assert!(r.is_ok());
assert_eq!(r.unwrap(), "default");
}
#[test]
fn run_text_trims_input() {
let opts = TextPromptOptions {
message: "X?".into(),
initial: None,
};
let mut stdin = Cursor::new(b" spaced \n");
let mut stdout = Vec::new();
let r = run_text(&opts, &mut stdin, &mut stdout);
assert!(r.is_ok());
assert_eq!(r.unwrap(), "spaced");
}
#[test]
fn run_text_empty_input_no_initial_returns_empty_string() {
let opts = TextPromptOptions {
message: "Name?".into(),
initial: None,
};
let mut stdin = Cursor::new(b"\n");
let mut stdout = Vec::new();
let r = run_text(&opts, &mut stdin, &mut stdout);
assert!(r.is_ok());
assert_eq!(r.unwrap(), "");
}
#[test]
fn run_text_whitespace_only_treated_as_empty_uses_initial() {
let opts = TextPromptOptions {
message: "X?".into(),
initial: Some("default".into()),
};
let mut stdin = Cursor::new(b" \n");
let mut stdout = Vec::new();
let r = run_text(&opts, &mut stdin, &mut stdout);
assert!(r.is_ok());
assert_eq!(r.unwrap(), "default");
}
}