stdin_nonblocking/
lib.rs

1#[cfg(doctest)]
2doc_comment::doctest!("../README.md");
3
4use std::io::{self, BufRead};
5use std::sync::mpsc::{self, Receiver, Sender};
6use std::thread;
7use std::time::Duration;
8
9/// Spawns a background thread that continuously reads from stdin as a stream.
10///
11/// This function returns an `mpsc Receiver`, allowing non-blocking polling
12/// of stdin input just like `spawn_stdin_channel`.
13///
14/// # Returns
15/// A `Receiver<String>` that emits lines from stdin.
16///
17/// # Example
18/// ```
19/// use stdin_nonblocking::spawn_stdin_stream;
20/// use std::sync::mpsc::TryRecvError;
21/// use std::time::Duration;
22///
23/// let stdin_stream = spawn_stdin_stream();
24///
25/// loop {
26///     match stdin_stream.try_recv() {
27///         Ok(line) => println!("Received: {}", line),
28///         Err(TryRecvError::Empty) => {
29///             // No input yet; continue execution
30///         }
31///         Err(TryRecvError::Disconnected) => {
32///             println!("Input stream closed. Exiting...");
33///             break;
34///         }
35///     }
36///     std::thread::sleep(Duration::from_millis(500));
37/// }
38/// ```
39pub fn spawn_stdin_stream() -> Receiver<String> {
40    let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();
41
42    thread::spawn(move || {
43        let stdin = io::stdin();
44        let mut stdin_lock = stdin.lock();
45
46        loop {
47            let mut buffer = String::new();
48            match stdin_lock.read_line(&mut buffer) {
49                Ok(0) => break, // EOF detected, exit thread
50                Ok(_) => {
51                    if tx.send(buffer.trim().to_string()).is_err() {
52                        break; // Exit if receiver is dropped
53                    }
54                }
55                Err(_) => break, // Read failure
56            }
57        }
58    });
59
60    rx
61}
62
63/// Reads from stdin if available, otherwise returns a default value.
64///
65/// **Non-blocking:** This function polls `stdin` once and immediately returns.
66/// If no input is available within the polling time, it returns the provided default value.
67///
68/// # Arguments
69/// * `default` - An optional fallback value returned if no input is available.
70///
71/// # Returns
72/// * `Option<String>` - The trimmed `stdin` input as a `String` if available, or the provided `default` as a `String` if no input is received.
73///
74/// # Example
75/// ```
76/// use stdin_nonblocking::get_stdin_or_default;
77///
78/// let input = get_stdin_or_default(Some("fallback_value"));
79///
80/// assert_eq!(input, Some("fallback_value".to_string()));
81/// ```
82pub fn get_stdin_or_default(default: Option<&str>) -> Option<String> {
83    let stdin_channel = spawn_stdin_stream();
84    let mut input = String::new();
85
86    // Give the reader thread a short time to capture any available input
87    thread::sleep(Duration::from_millis(50));
88
89    while let Ok(line) = stdin_channel.try_recv() {
90        input.push_str(&line); // Collect all lines
91        input.push('\n'); // Add a newline between lines
92    }
93
94    // If input was collected, return it. Otherwise, return the default value.
95    if !input.trim().is_empty() {
96        Some(input.trim().to_string())
97    } else {
98        default.map(|s| s.to_string())
99    }
100}