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
use crate::errors::*;
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;
use rustyline::error::ReadlineError;
use std::iter;
use std::str::FromStr;

pub fn random_string(len: usize) -> String {
    let mut rng = thread_rng();
    iter::repeat(())
        .map(|()| rng.sample(Alphanumeric))
        .map(char::from)
        .take(len)
        .collect()
}

pub fn read_line(prompt: &str) -> Result<String> {
    let mut rl = rustyline::Editor::<()>::new();
    let mut line = rl.readline(prompt)
        .map_err(|err| match err {
            ReadlineError::Eof => format_err!("Failed to read line from input"),
            ReadlineError::Interrupted => format_err!("Prompt has been canceled"),
            err => err.into(),
        })?;
    if let Some(idx) = line.find('\n') {
        line.truncate(idx);
    }
    info!("Read from prompt: {:?}", line);
    Ok(line)
}

pub fn question(text: &str) -> Result<String> {
    let prompt = format!("\x1b[1m[\x1b[34m?\x1b[0;1m]\x1b[0m {}: ", text);
    read_line(&prompt)
}

pub fn question_opt(text: &str) -> Result<Option<String>> {
    let answer = question(text)?;

    if !answer.is_empty() {
        Ok(Some(answer))
    } else {
        Ok(None)
    }
}

pub fn question_typed_opt<T: FromStr>(text: &str) -> Result<Option<T>> {
    let answer = question(text)?;

    if !answer.is_empty() {
        let answer = answer.parse()
            .map_err(|_| format_err!("Failed to parse input"))?;
        Ok(Some(answer))
    } else {
        Ok(None)
    }
}

pub fn question_or<I: Into<String>>(text: &str, default: I) -> Result<String> {
    let default = default.into();
    let answer = question(&format!("{} [{}]", text, default))?;

    if !answer.is_empty() {
        Ok(answer)
    } else {
        Ok(default)
    }
}

#[inline]
fn yes_no(text: &str, default: bool) -> Result<bool> {
    let answer = question(text)?;
    let answer = answer.to_lowercase();

    Ok(match answer.as_str() {
        "" => default,
        "y" => true,
        "n" => false,
        _ => bail!("invalid input"),
    })
}

pub fn yes_else_no(text: &str) -> Result<bool> {
    yes_no(&format!("{} [Y/n]", text), true)
}

pub fn no_else_yes(text: &str) -> Result<bool> {
    yes_no(&format!("{} [y/N]", text), false)
}