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());
    }
}