1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
use crate::cli::error::CliError::UserStopSync; use anyhow::Result; use colored::*; use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use serde::export::fmt::Debug; use std::fmt::Write; use std::io; use std::string::ToString; pub fn confirm<W, E>(mut writer: W, question: &str, e: Vec<E>) -> Result<E> where W: io::Write, E: EnumConfirm + Clone + Debug, { let mut write_question_line = || writeln!(writer, "{} : {}", question, &e.to_string()).unwrap(); write_question_line(); enable_raw_mode()?; let e = loop { if let Event::Key(event) = read()? { if event == KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL) || event == KeyEvent::new(KeyCode::Char('z'), KeyModifiers::CONTROL) { disable_raw_mode()?; bail!(UserStopSync); } if let Some(e) = e.iter().find_map(|e| { if event == KeyCode::Char(e.to_char()).into() { Some(e) } else { None } }) { break e; } else { disable_raw_mode()?; write_question_line(); enable_raw_mode()?; } } }; disable_raw_mode()?; Ok(e.clone()) } pub trait EnumConfirm { type T: EnumConfirm + Sized; fn to_vec() -> Vec<Self::T>; fn to_char(&self) -> char; } pub trait ToStringEnumConfirm { fn to_string(&self) -> String; } impl<E> ToStringEnumConfirm for Vec<E> where E: EnumConfirm, { fn to_string(&self) -> String { let mut buf = String::new(); write!(&mut buf, "[").unwrap(); for (i, e) in self.iter().enumerate() { if i > 0 { write!(&mut buf, ",").unwrap(); } write!(&mut buf, "{}", e.to_char().to_string().bold()).unwrap(); } write!(&mut buf, "]").unwrap(); buf } } #[macro_export] macro_rules! enum_confirm { ($i :ident, $($it: ident), +) => { #[derive(Debug, Eq , PartialEq, Clone)] pub enum $i { $( #[allow(non_camel_case_types)] $it, )+ } impl EnumConfirm for $i { type T = Self; fn to_vec() -> Vec<Self::T> { vec![$( $i::$it, )+] } fn to_char(&self) -> char { match self { $( $i::$it => stringify!($it).chars().next().unwrap(), )+ } } } }; } #[cfg(test)] mod tests { use crate::cli::terminal::confirm::EnumConfirm; use crate::cli::terminal::confirm::ToStringEnumConfirm; enum_confirm!(EnumConfirmTest, y, Y, n); #[test] fn enum_confirm_macro() { let actual = EnumConfirmTest::to_vec(); let expected = vec![EnumConfirmTest::y, EnumConfirmTest::Y, EnumConfirmTest::n]; assert_eq!(actual, expected); } #[test] fn enum_confirm_to_string() { let actual = EnumConfirmTest::to_vec(); assert_eq!(actual.to_string(), "[y,Y,n]".to_string()); } }