challenge_prompt/
lib.rs

1//! `challenge_prompt` makes the user pause before doing something.
2//!
3//! A "challenge" prompt introduces a hurdle the user has to pass before
4//! continuing. This is useful in deployment scripts or in scary commands to
5//! make sure they were not typed by rote or pulled out of a shell history by
6//! mistake.
7//!
8//! Available prompts are:
9//! - arithmetic: Asks the user to solve a problem like `(12 + 7) mod 4`,
10//! - phrase: Asks the user to type in a phrase like "I am probably making a
11//!   mistake" exactly,
12//! - yes: Asks the user to type in 'y' or 'yes'.
13//!
14//! This crate is both a library and a small command line application for use in
15//! shell scripts.
16//!
17//! ## Command-line example
18//!
19//! ```ignore
20//! $ cargo install challenge-prompt
21//! $ challenge-prompt
22//! Solve: (5 + 9) mod 6 = ?
23//! ```
24//!
25//! ## Library example
26//!
27//! ```toml
28//! [dependencies]
29//! challenge_prompt = "0.3"
30//! ```
31//!
32//! ```no_run
33//! extern crate challenge_prompt;
34//!
35//! let mut rng = challenge_prompt::Rng::new_from_time().unwrap();
36//! if !challenge_prompt::Challenge::Arithmetic.prompt(&mut rng) {
37//!    panic!("user failed the challenge")
38//! }
39//! ```
40
41mod rng;
42
43#[derive(Debug)]
44pub enum Challenge {
45    Arithmetic,
46    Phrase(String),
47    Yes,
48}
49
50pub use rng::{Rng, RngError};
51
52pub const DEFAULT_PHRASE: &str = "I am probably making a mistake.";
53
54impl Challenge {
55    /// Prompt the user with the challenge and return whether they passed or
56    /// not.
57    pub fn prompt(&self, rng: &mut rng::Rng) -> bool {
58        use Challenge::*;
59        match self {
60            Arithmetic => {
61                let a = rng.u32() % 20;
62                let b = rng.u32() % 20;
63                let c = rng.u32() % 18 + 2;
64                prompt_gen(
65                    &format!("Solve: ({} + {}) mod {} = ?", a, b, c),
66                    &[&format!("{}", (a + b) % c)],
67                )
68            }
69            Phrase(str) => prompt_gen(
70                &format!("Enter the following exactly to continue: {}", str),
71                &[str],
72            ),
73            Yes => prompt_gen("Continue? [y]", &["y", "yes"]),
74        }
75    }
76}
77
78/// Generic prompt implementation: print out the `prompt`, read a line of input,
79/// and return whether it matches `expected` or not.
80pub fn prompt_gen(prompt: &str, expected: &[&str]) -> bool {
81    use std::io;
82    println!("{}", prompt);
83    let mut input = String::new();
84    loop {
85        if io::stdin().read_line(&mut input).is_ok() {
86            let input = input.trim();
87            return expected.iter().any(|&e| e == input);
88        }
89    }
90}