converted remaining tui module to crossterm
This commit is contained in:
parent
ebe31b6df7
commit
5a262650a0
4 changed files with 85 additions and 38 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
19
src/demos.rs
19
src/demos.rs
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
82
src/tui.rs
82
src/tui.rs
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue