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}