stdin_nonblocking/
lib.rs

1#[cfg(doctest)]
2doc_comment::doctest!("../README.md");
3
4use std::io::{self, IsTerminal, Read};
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 binary stream.
10///
11/// This function returns an `mpsc Receiver`, allowing non-blocking polling
12/// of stdin input just like `spawn_stdin_channel`.
13///
14/// **Handling Interactive Mode:**
15/// - If stdin is a terminal (interactive mode), this function immediately returns an empty receiver.
16/// - This prevents blocking behavior when running interactively.
17/// - When reading from a file or pipe, the background thread captures input **as raw bytes**.
18///
19/// # Returns
20/// A `Receiver<Vec<u8>>` that emits **binary data** from stdin.
21///
22/// # Example
23/// ```
24/// use stdin_nonblocking::spawn_stdin_stream;
25/// use std::sync::mpsc::TryRecvError;
26/// use std::time::Duration;
27///
28/// let stdin_stream = spawn_stdin_stream();
29///
30/// loop {
31///     match stdin_stream.try_recv() {
32///         Ok(bytes) => println!("Received: {:?}", bytes), // Always raw bytes
33///         Err(TryRecvError::Empty) => {
34///             // No input yet; continue execution
35///         }
36///         Err(TryRecvError::Disconnected) => {
37///             println!("Input stream closed. Exiting...");
38///             break;
39///         }
40///     }
41///     std::thread::sleep(Duration::from_millis(500));
42/// }
43/// ```
44pub fn spawn_stdin_stream() -> Receiver<Vec<u8>> {
45    let (tx, rx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = mpsc::channel();
46
47    // If stdin is a terminal, return early (no blocking).
48    if io::stdin().is_terminal() {
49        return rx;
50    }
51
52    thread::spawn(move || {
53        let mut buffer = Vec::new();
54        let stdin = io::stdin();
55        let mut stdin_lock = stdin.lock();
56
57        match stdin_lock.read_to_end(&mut buffer) {
58            Ok(0) => (), // EOF, no data
59            Ok(_) => {
60                let _ = tx.send(buffer); // Send full binary data
61            }
62            Err(_) => (), // Read failure
63        }
64    });
65
66    rx
67}
68
69/// Reads from stdin if available, otherwise returns a default value.
70///
71/// **Non-blocking:** This function polls `stdin` once and immediately returns.
72/// If no input is available within the polling time, it returns the provided default value.
73///
74/// **Handling Interactive Mode:**
75/// - If running interactively (stdin is a terminal), this function returns the default value immediately.
76/// - This prevents hanging while waiting for user input in interactive sessions.
77/// - When used with redirected input (e.g., from a file or pipe), it collects available **binary** input.
78///
79/// # Arguments
80/// * `default` - An optional fallback value returned if no input is available.
81///
82/// # Returns
83/// * `Option<Vec<u8>>` - The full stdin input (or default value as bytes).
84///
85/// # Example
86/// ```
87/// use stdin_nonblocking::get_stdin_or_default;
88///
89/// let input = get_stdin_or_default(Some(b"fallback_value"));
90///
91/// assert_eq!(input, Some(b"fallback_value".to_vec()));
92/// ```
93pub fn get_stdin_or_default(default: Option<&[u8]>) -> Option<Vec<u8>> {
94    let stdin_channel = spawn_stdin_stream();
95
96    // Give the reader thread a short time to capture any available input
97    thread::sleep(Duration::from_millis(50));
98
99    if let Ok(data) = stdin_channel.try_recv() {
100        return Some(data);
101    }
102
103    // Return `None` if no default
104    default?;
105
106    // No input available, return the default value
107    Some(default.unwrap_or(b"").to_vec())
108}