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
//! `challenge_prompt` makes the user pause before doing something.
//!
//! A "challenge" prompt introduces a hurdle the user has to pass before
//! continuing. This is useful in deployment scripts or in scary commands to
//! make sure they were not typed by rote or pulled out of a shell history by
//! mistake.
//!
//! Available prompts are:
//! - arithmetic: Asks the user to solve a problem like `(12 + 7) mod 4`,
//! - phrase: Asks the user to type in a phrase like "I am probably making a
//!   mistake" exactly,
//! - yes: Asks the user to type in 'y' or 'yes'.
//!
//! This crate is both a library and a small command line application for use in
//! shell scripts.
//!
//! ## Command-line example
//!
//! ```ignore
//! $ cargo install challenge-prompt
//! $ challenge-prompt
//! Solve: (5 + 9) mod 6 = ?
//! ```
//!
//! ## Library example
//!
//! ```toml
//! [dependencies]
//! challenge_prompt = "0.3"
//! ```
//!
//! ```no_run
//! extern crate challenge_prompt;
//!
//! let mut rng = challenge_prompt::Rng::new_from_time().unwrap();
//! if !challenge_prompt::Challenge::Arithmetic.prompt(&mut rng) {
//!    panic!("user failed the challenge")
//! }
//! ```

mod rng;

#[derive(Debug)]
pub enum Challenge {
    Arithmetic,
    Phrase(String),
    Yes,
}

pub use rng::{Rng, RngError};

pub const DEFAULT_PHRASE: &str = "I am probably making a mistake.";

impl Challenge {
    /// Prompt the user with the challenge and return whether they passed or
    /// not.
    pub fn prompt(&self, rng: &mut rng::Rng) -> bool {
        use Challenge::*;
        match self {
            Arithmetic => {
                let a = rng.u32() % 20;
                let b = rng.u32() % 20;
                let c = rng.u32() % 18 + 2;
                prompt_gen(
                    &format!("Solve: ({} + {}) mod {} = ?", a, b, c),
                    &[&format!("{}", (a + b) % c)],
                )
            }
            Phrase(str) => prompt_gen(
                &format!("Enter the following exactly to continue: {}", str),
                &[str],
            ),
            Yes => prompt_gen("Continue? [y]", &["y", "yes"]),
        }
    }
}

/// Generic prompt implementation: print out the `prompt`, read a line of input,
/// and return whether it matches `expected` or not.
pub fn prompt_gen(prompt: &str, expected: &[&str]) -> bool {
    use std::io;
    println!("{}", prompt);
    let mut input = String::new();
    loop {
        if io::stdin().read_line(&mut input).is_ok() {
            let input = input.trim();
            return expected.iter().any(|&e| e == input);
        }
    }
}