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