stdin_nonblocking/
lib.rs

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