66 lines
2.5 KiB
Rust
66 lines
2.5 KiB
Rust
//! Interactive prompt helpers for the CLI.
|
|
//!
|
|
//! The `prompt`/`prompt_optional`/`prompt_secret` family reads from stdin /
|
|
//! the TTY; the `prompt_keep`/`prompt_keep_opt`/`prompt_yesno` variants are
|
|
//! used by the edit handlers to keep current values when the user hits enter
|
|
//! at a blank prompt. `prompt_secret` honours `RELICARIO_TEST_ITEM_SECRET`
|
|
//! so integration tests (which don't have a TTY) can inject secrets.
|
|
|
|
use anyhow::Result;
|
|
|
|
/// `rpassword::prompt_password` wrapper that honours `RELICARIO_TEST_ITEM_SECRET`
|
|
/// for integration-test use (rpassword reads /dev/tty by default, which is
|
|
/// unavailable in assert_cmd-spawned children).
|
|
pub(crate) fn prompt_secret(label: &str) -> Result<String> {
|
|
if let Some(s) = crate::test_item_secret_override() {
|
|
return Ok(s);
|
|
}
|
|
rpassword::prompt_password(label).map_err(Into::into)
|
|
}
|
|
|
|
pub(crate) fn prompt(label: &str) -> Result<String> {
|
|
eprint!("{label}: ");
|
|
std::io::Write::flush(&mut std::io::stderr())?;
|
|
let mut s = String::new();
|
|
std::io::stdin().read_line(&mut s)?;
|
|
let trimmed = s.trim().to_string();
|
|
if trimmed.is_empty() { anyhow::bail!("{label} required"); }
|
|
Ok(trimmed)
|
|
}
|
|
|
|
pub(crate) fn prompt_optional(label: &str) -> Result<Option<String>> {
|
|
eprint!("{label} (leave blank to skip): ");
|
|
std::io::Write::flush(&mut std::io::stderr())?;
|
|
let mut s = String::new();
|
|
std::io::stdin().read_line(&mut s)?;
|
|
let trimmed = s.trim().to_string();
|
|
Ok(if trimmed.is_empty() { None } else { Some(trimmed) })
|
|
}
|
|
|
|
pub(crate) fn prompt_keep(label: &str, current: &str) -> Result<Option<String>> {
|
|
eprint!("{label} [{current}]: ");
|
|
std::io::Write::flush(&mut std::io::stderr())?;
|
|
let mut s = String::new();
|
|
std::io::stdin().read_line(&mut s)?;
|
|
let trimmed = s.trim().to_string();
|
|
Ok(if trimmed.is_empty() { None } else { Some(trimmed) })
|
|
}
|
|
|
|
pub(crate) fn prompt_keep_opt(label: &str, current: Option<&str>) -> Result<Option<String>> {
|
|
let display = current.unwrap_or("(none)");
|
|
eprint!("{label} [{display}]: ");
|
|
std::io::Write::flush(&mut std::io::stderr())?;
|
|
let mut s = String::new();
|
|
std::io::stdin().read_line(&mut s)?;
|
|
let trimmed = s.trim().to_string();
|
|
Ok(if trimmed.is_empty() { None } else { Some(trimmed) })
|
|
}
|
|
|
|
pub(crate) fn prompt_yesno(label: &str) -> Result<bool> {
|
|
eprint!("{label} [y/N] ");
|
|
std::io::Write::flush(&mut std::io::stderr())?;
|
|
let mut s = String::new();
|
|
std::io::stdin().read_line(&mut s)?;
|
|
Ok(matches!(s.trim().to_ascii_lowercase().as_str(), "y" | "yes"))
|
|
}
|