console_listener/lib.rs
1//! `console_listener` includes the `Listener` struct, used to listen for a specified string to be typed to the console.
2
3pub mod listener {
4 use std::{
5 any::Any,
6 io,
7 sync::{Arc, Mutex},
8 thread,
9 };
10
11 /// The Listener struct holds a thread that listens for a specified string written to the console.
12 ///
13 /// The Listener struct implements functions to check whether the string was typed, `heard_key`, and to wait for the string to get typed, `wait_for_key`.
14 /// Create a new instance of Listener using `new` or `new_case_insensitive` and specify the string to listen for.
15 pub struct Listener {
16 heard_key: Arc<Mutex<bool>>,
17 thread: thread::JoinHandle<bool>,
18 }
19
20 impl Listener {
21 /// Creates a new Listener instance.
22 ///
23 /// The key is the string that the Listener listens for.
24 ///
25 /// # Panics
26 ///
27 /// The `new` function panics if the key is an empty string
28 ///
29 /// # Examples
30 ///
31 /// ```
32 /// let listener = Listener::new("q");
33 /// ```
34 pub fn new(key: &str) -> Listener {
35 assert!(!key.is_empty());
36
37 Self::new_inner(key, false)
38 }
39
40 /// Creates a new case insensitive Listener instance.
41 ///
42 /// The key is the string that the Listener listens for.
43 ///
44 /// # Panics
45 ///
46 /// The `new_case_insensitive` function panics if the key is an empty string
47 ///
48 /// # Examples
49 ///
50 /// ```
51 /// let listener = Listener::new_case_insensitive("q");
52 /// ```
53 pub fn new_case_insensitive(key: &str) -> Listener {
54 assert!(!key.is_empty());
55
56 Self::new_inner(key, true)
57 }
58
59 fn new_inner(key: &str, case_insensitive: bool) -> Listener {
60 let heard_key = Arc::new(Mutex::new(false));
61 let thread;
62 {
63 let key = String::from(key);
64 let heard_key = Arc::clone(&heard_key);
65 thread = thread::spawn(move || loop {
66 let mut input = String::with_capacity(key.len());
67 io::stdin()
68 .read_line(&mut input)
69 .expect("Error while reading input from console.");
70
71 let input = input.trim_end();
72
73 if case_insensitive && input.eq_ignore_ascii_case(&key) || input == key {
74 let mut b = heard_key.lock().expect("Failed to lock heard_key.");
75 *b = true;
76
77 break true;
78 }
79 });
80 }
81
82 Listener { heard_key, thread }
83 }
84
85 /// Returns true if the Listener has heard the key.
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// let listener = Listener::new("q");
91 ///
92 /// while !listener.heard_key() {
93 /// println!("Waiting for key.");
94 /// }
95 /// ```
96 pub fn heard_key(&self) -> bool {
97 match self.heard_key.lock() {
98 Ok(b) => *b,
99 Err(e) => *e.into_inner(),
100 }
101 }
102
103 /// Blocks the current thread until the listening thread ends. This consumes the Listener.
104 ///
105 /// Returns `Ok` containing a boolean indicating whether the key was heard.
106 ///
107 /// # Errors
108 ///
109 /// Returns `Err` if the listening thread panicked.
110 ///
111 /// # Examples
112 ///
113 /// ```
114 /// let listener = Listener::new("q");
115 ///
116 /// heard_key = listener.wait_for_key().unwrap();
117 ///
118 /// assert!(heard_key);
119 /// ```
120 pub fn wait_for_key(self) -> Result<bool, Box<dyn Any + Send>> {
121 self.thread.join()
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::{listener::*};
129 use std::{thread, time::Duration};
130
131 #[test]
132 fn new_works() {
133 let _listener = Listener::new("test");
134 }
135
136 #[test]
137 #[should_panic]
138 fn new_panics_on_empty_key() {
139 let _listener = Listener::new("");
140 }
141
142 #[test]
143 fn new_case_insensitive_works() {
144 let _listener = Listener::new_case_insensitive("test");
145 }
146
147 #[test]
148 #[should_panic]
149 fn new_case_insensitive_panics_on_empty_key() {
150 let _listener = Listener::new_case_insensitive("");
151 }
152
153 #[test]
154 fn heard_key_returns_false() {
155 let listener = Listener::new("test");
156
157 thread::sleep(Duration::from_secs(5));
158
159 assert!(!listener.heard_key());
160 }
161}