main
rs 47 lines 1.47 KB
Raw
1 //! Interactive prompts, kept in one place so every command fails the same way
2 //! when stdin isn't a TTY (CI, pipes) instead of hanging.
3
4 use {
5 anyhow::{anyhow, Result},
6 console::Term,
7 dialoguer::{theme::ColorfulTheme, Confirm, Input, Password},
8 };
9
10 fn require_tty(what: &str) -> Result<()> {
11 if Term::stdout().features().is_attended() {
12 Ok(())
13 } else {
14 Err(anyhow!(
15 "{what} requires an interactive terminal. Provide it via a flag or environment \
16 variable instead when running non-interactively."
17 ))
18 }
19 }
20
21 pub fn text(label: &str) -> Result<String> {
22 require_tty(label)?;
23 Ok(Input::with_theme(&ColorfulTheme::default())
24 .with_prompt(label)
25 .interact_text()?)
26 }
27
28 pub fn password(label: &str) -> Result<String> {
29 require_tty(label)?;
30 Ok(Password::with_theme(&ColorfulTheme::default())
31 .with_prompt(label)
32 .interact()?)
33 }
34
35 /// A yes/no confirmation. Non-interactive callers should not reach this at
36 /// all for destructive actions — check `--yes` first — but reads default to
37 /// `default` here rather than erroring, since a TTY-less "confirm the details
38 /// I already showed you" is reasonable to skip.
39 pub fn confirm(label: &str, default: bool) -> Result<bool> {
40 if !Term::stdout().features().is_attended() {
41 return Ok(default);
42 }
43 Ok(Confirm::with_theme(&ColorfulTheme::default())
44 .with_prompt(label)
45 .default(default)
46 .interact()?)
47 }