converted remaining tui module to crossterm

This commit is contained in:
Carson McManus 2022-06-25 10:57:37 -04:00
parent ebe31b6df7
commit 5a262650a0
4 changed files with 85 additions and 38 deletions

View file

@ -99,6 +99,10 @@ impl FromStr for Verbosity {
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
#[clap(about = "Debug stuff, not useful for most users.")] #[clap(about = "Debug stuff, not useful for most users.")]
pub(crate) struct ArgsDebug { pub(crate) struct ArgsDebug {
#[clap(long, help = "Show a text prompt.")]
pub demo_prompt: bool,
#[clap(long, help = "Show a character prompt.")]
pub demo_prompt_char: bool,
#[clap(long, help = "Show an example confirmation menu using dummy data.")] #[clap(long, help = "Show an example confirmation menu using dummy data.")]
pub demo_conf_menu: bool, pub demo_conf_menu: bool,
} }

View file

@ -2,6 +2,22 @@ use crate::tui;
use log::*; use log::*;
use steamguard::{Confirmation, ConfirmationType}; use steamguard::{Confirmation, ConfirmationType};
pub fn demo_prompt() {
print!("Prompt: ");
let result = tui::prompt();
println!("Result: {}", result);
}
pub fn demo_prompt_char() {
println!("Showing prompt");
let result = tui::prompt_char("Continue?", "yn");
println!("Result: {}", result);
let result = tui::prompt_char("Continue?", "Yn");
println!("Result: {}", result);
let result = tui::prompt_char("Continue?", "yN");
println!("Result: {}", result);
}
pub fn demo_confirmation_menu() { pub fn demo_confirmation_menu() {
info!("showing demo menu"); info!("showing demo menu");
let (accept, deny) = tui::prompt_confirmation_menu(vec![ let (accept, deny) = tui::prompt_confirmation_menu(vec![
@ -33,6 +49,7 @@ pub fn demo_confirmation_menu() {
creator: 09870987, creator: 09870987,
description: "example confirmation".into(), description: "example confirmation".into(),
}, },
]).expect("confirmation menu demo failed"); ])
.expect("confirmation menu demo failed");
println!("accept: {}, deny: {}", accept.len(), deny.len()); println!("accept: {}, deny: {}", accept.len(), deny.len());
} }

View file

@ -329,6 +329,12 @@ fn load_accounts_with_prompts(manifest: &mut accountmanager::Manifest) -> anyhow
} }
fn do_subcmd_debug(args: cli::ArgsDebug) -> anyhow::Result<()> { fn do_subcmd_debug(args: cli::ArgsDebug) -> anyhow::Result<()> {
if args.demo_prompt {
demos::demo_prompt();
}
if args.demo_prompt_char {
demos::demo_prompt_char();
}
if args.demo_conf_menu { if args.demo_conf_menu {
demos::demo_confirmation_menu(); demos::demo_confirmation_menu();
} }

View file

@ -2,16 +2,15 @@ use crossterm::{
cursor, cursor,
event::{Event, KeyCode, KeyEvent, KeyModifiers}, event::{Event, KeyCode, KeyEvent, KeyModifiers},
execute, execute,
style::{Print, PrintStyledContent, Stylize, Color, SetForegroundColor}, style::{Color, Print, PrintStyledContent, SetForegroundColor, Stylize},
terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
QueueableCommand, QueueableCommand,
}; };
use log::*; use log::*;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::io::{stdin, stdout, Read, Write}; use std::io::{stdout, Write};
use steamguard::Confirmation; use steamguard::Confirmation;
use termion::{input::TermRead, raw::IntoRawMode};
lazy_static! { lazy_static! {
static ref CAPTCHA_VALID_CHARS: Regex = static ref CAPTCHA_VALID_CHARS: Regex =
@ -42,12 +41,22 @@ fn test_validate_captcha_text() {
/// Prompt the user for text input. /// Prompt the user for text input.
pub(crate) fn prompt() -> String { pub(crate) fn prompt() -> String {
let mut text = String::new(); stdout().flush().unwrap();
let _ = std::io::stdout().flush();
std::io::stdin() let mut line = String::new();
.read_line(&mut text) while let Event::Key(KeyEvent { code, .. }) = crossterm::event::read().unwrap() {
.expect("Did not enter a correct string"); match code {
return String::from(text.strip_suffix('\n').unwrap()); KeyCode::Enter => {
break;
}
KeyCode::Char(c) => {
line.push(c);
}
_ => {}
}
}
line
} }
pub(crate) fn prompt_captcha_text(captcha_gid: &String) -> String { pub(crate) fn prompt_captcha_text(captcha_gid: &String) -> String {
@ -68,10 +77,20 @@ pub(crate) fn prompt_captcha_text(captcha_gid: &String) -> String {
/// ///
/// `chars` should be all lowercase characters, with at most 1 uppercase character. The uppercase character is the default answer if no answer is provided. /// `chars` should be all lowercase characters, with at most 1 uppercase character. The uppercase character is the default answer if no answer is provided.
pub(crate) fn prompt_char(text: &str, chars: &str) -> char { pub(crate) fn prompt_char(text: &str, chars: &str) -> char {
return prompt_char_impl(&mut std::io::stdin(), text, chars); loop {
let _ = stdout().queue(Print(format!("{} [{}] ", text, chars)));
let _ = stdout().flush();
let input = prompt();
if let Ok(c) = prompt_char_impl(input, chars) {
return c;
}
}
} }
fn prompt_char_impl(input: &mut impl Read, text: &str, chars: &str) -> char { fn prompt_char_impl<T>(input: T, chars: &str) -> anyhow::Result<char>
where
T: Into<String>,
{
let uppers = chars.replace(char::is_lowercase, ""); let uppers = chars.replace(char::is_lowercase, "");
if uppers.len() > 1 { if uppers.len() > 1 {
panic!("Invalid chars for prompt_char. Maximum 1 uppercase letter is allowed."); panic!("Invalid chars for prompt_char. Maximum 1 uppercase letter is allowed.");
@ -82,27 +101,24 @@ fn prompt_char_impl(input: &mut impl Read, text: &str, chars: &str) -> char {
None None
}; };
loop { let answer: String = input.into().to_ascii_lowercase();
print!("{} [{}] ", text, chars);
let _ = std::io::stdout().flush();
let answer = input
.read_line()
.expect("Unable to read input")
.unwrap()
.to_ascii_lowercase();
if answer.len() == 0 { if answer.len() == 0 {
if let Some(a) = default_answer { if let Some(a) = default_answer {
return a; return Ok(a);
} else {
bail!("no valid answer")
} }
} else if answer.len() > 1 { } else if answer.len() > 1 {
continue; bail!("answer too long")
} }
let answer_char = answer.chars().collect::<Vec<char>>()[0]; let answer_char = answer.chars().collect::<Vec<char>>()[0];
if chars.to_ascii_lowercase().contains(answer_char) { if chars.to_ascii_lowercase().contains(answer_char) {
return answer_char; return Ok(answer_char);
}
} }
bail!("no valid answer")
} }
/// Returns a tuple of (accepted, denied). Ignored confirmations are not included. /// Returns a tuple of (accepted, denied). Ignored confirmations are not included.
@ -254,9 +270,13 @@ pub(crate) fn prompt_confirmation_menu(
pub(crate) fn pause() { pub(crate) fn pause() {
println!("Press any key to continue..."); println!("Press any key to continue...");
let mut stdout = std::io::stdout().into_raw_mode().unwrap(); let _ = stdout().flush();
stdout.flush().unwrap(); loop {
std::io::stdin().events().next(); match crossterm::event::read().expect("could not read terminal events") {
Event::Key(_) => break,
_ => continue,
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -266,35 +286,35 @@ mod prompt_char_tests {
#[test] #[test]
fn test_gives_answer() { fn test_gives_answer() {
let inputs = ['y', '\n'].iter().collect::<String>(); let inputs = ['y', '\n'].iter().collect::<String>();
let answer = prompt_char_impl(&mut inputs.as_bytes(), "ligma balls", "yn"); let answer = prompt_char_impl(inputs, "yn").unwrap();
assert_eq!(answer, 'y'); assert_eq!(answer, 'y');
} }
#[test] #[test]
fn test_gives_default() { fn test_gives_default() {
let inputs = ['\n'].iter().collect::<String>(); let inputs = ['\n'].iter().collect::<String>();
let answer = prompt_char_impl(&mut inputs.as_bytes(), "ligma balls", "Yn"); let answer = prompt_char_impl(inputs, "Yn").unwrap();
assert_eq!(answer, 'y'); assert_eq!(answer, 'y');
} }
#[test] #[test]
fn test_should_not_give_default() { fn test_should_not_give_default() {
let inputs = ['n', '\n'].iter().collect::<String>(); let inputs = ['n', '\n'].iter().collect::<String>();
let answer = prompt_char_impl(&mut inputs.as_bytes(), "ligma balls", "Yn"); let answer = prompt_char_impl(inputs, "Yn").unwrap();
assert_eq!(answer, 'n'); assert_eq!(answer, 'n');
} }
#[test] #[test]
fn test_should_not_give_invalid() { fn test_should_not_give_invalid() {
let inputs = ['g', '\n', 'n', '\n'].iter().collect::<String>(); let inputs = ['g', '\n', 'n', '\n'].iter().collect::<String>();
let answer = prompt_char_impl(&mut inputs.as_bytes(), "ligma balls", "yn"); let answer = prompt_char_impl(inputs, "yn").unwrap();
assert_eq!(answer, 'n'); assert_eq!(answer, 'n');
} }
#[test] #[test]
fn test_should_not_give_multichar() { fn test_should_not_give_multichar() {
let inputs = ['y', 'y', '\n', 'n', '\n'].iter().collect::<String>(); let inputs = ['y', 'y', '\n', 'n', '\n'].iter().collect::<String>();
let answer = prompt_char_impl(&mut inputs.as_bytes(), "ligma balls", "yn"); let answer = prompt_char_impl(inputs, "yn").unwrap();
assert_eq!(answer, 'n'); assert_eq!(answer, 'n');
} }
} }