console-listener 0.2.0

A crate for listening to console input in a separate thread.
Documentation
//! `console_listener` includes the `Listener` struct, used to listen for a specified string to be typed to the console.

pub mod listener {
    use std::{
        any::Any,
        io,
        sync::{Arc, Mutex},
        thread,
    };

    /// The Listener struct holds a thread that listens for a specified string written to the console.
    /// 
    /// 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`.
    /// Create a new instance of Listener using `new` or `new_case_insensitive` and specify the string to listen for.
    pub struct Listener {
        heard_key: Arc<Mutex<bool>>,
        thread: thread::JoinHandle<bool>,
    }

    impl Listener {
        /// Creates a new Listener instance.
        ///
        /// The key is the string that the Listener listens for.
        ///
        /// # Panics
        ///
        /// The `new` function panics if the key is an empty string
        /// 
        /// # Examples
        /// 
        /// ```
        /// let listener = Listener::new("q");
        /// ```
        pub fn new(key: &str) -> Listener {
            assert!(!key.is_empty());

            Self::new_inner(key, false)
        }

        /// Creates a new case insensitive Listener instance.
        ///
        /// The key is the string that the Listener listens for.
        ///
        /// # Panics
        ///
        /// The `new_case_insensitive` function panics if the key is an empty string
        /// 
        /// # Examples
        /// 
        /// ```
        /// let listener = Listener::new_case_insensitive("q");
        /// ```
        pub fn new_case_insensitive(key: &str) -> Listener {
            assert!(!key.is_empty());

            Self::new_inner(key, true)
        }

        fn new_inner(key: &str, case_insensitive: bool) -> Listener {
            let heard_key = Arc::new(Mutex::new(false));
            let thread;
            {
                let key = String::from(key);
                let heard_key = Arc::clone(&heard_key);
                thread = thread::spawn(move || loop {
                    let mut input = String::with_capacity(key.len());
                    io::stdin()
                        .read_line(&mut input)
                        .expect("Error while reading input from console.");
                    
                    let input = input.trim_end();

                    if case_insensitive && input.eq_ignore_ascii_case(&key) || input == key {
                        let mut b = heard_key.lock().expect("Failed to lock heard_key.");
                        *b = true;

                        break true;
                    }
                });
            }

            Listener { heard_key, thread }
        }

        /// Returns true if the Listener has heard the key.
        /// 
        /// # Examples
        /// 
        /// ```
        /// let listener = Listener::new("q");
        /// 
        /// while !listener.heard_key() {
        ///     println!("Waiting for key.");
        /// }
        /// ```
        pub fn heard_key(&self) -> bool {
            match self.heard_key.lock() {
                Ok(b) => *b,
                Err(e) => *e.into_inner(),
            }
        }

        /// Blocks the current thread until the listening thread ends. This consumes the Listener.
        /// 
        /// Returns `Ok` containing a boolean indicating whether the key was heard.
        /// 
        /// # Errors
        /// 
        /// Returns `Err` if the listening thread panicked.
        /// 
        /// # Examples
        /// 
        /// ```
        /// let listener = Listener::new("q");
        /// 
        /// heard_key = listener.wait_for_key().unwrap();
        /// 
        /// assert!(heard_key);
        /// ```
        pub fn wait_for_key(self) -> Result<bool, Box<dyn Any + Send>> {
            self.thread.join()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{listener::*};
    use std::{thread, time::Duration};

    #[test]
    fn new_works() {
        let _listener = Listener::new("test");
    }

    #[test]
    #[should_panic]
    fn new_panics_on_empty_key() {
        let _listener = Listener::new("");
    }

    #[test]
    fn new_case_insensitive_works() {
        let _listener = Listener::new_case_insensitive("test");
    }

    #[test]
    #[should_panic]
    fn new_case_insensitive_panics_on_empty_key() {
        let _listener = Listener::new_case_insensitive("");
    }

    #[test]
    fn heard_key_returns_false() {
        let listener = Listener::new("test");

        thread::sleep(Duration::from_secs(5));

        assert!(!listener.heard_key());
    }
}