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}