stdin_nonblocking/lib.rs
1#[cfg(doctest)]
2doc_comment::doctest!("../README.md");
3
4use std::io::{self, BufRead};
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 stream.
10///
11/// This function returns an `mpsc Receiver`, allowing non-blocking polling
12/// of stdin input just like `spawn_stdin_channel`.
13///
14/// # Returns
15/// A `Receiver<String>` that emits lines from stdin.
16///
17/// # Example
18/// ```
19/// use stdin_nonblocking::spawn_stdin_stream;
20/// use std::sync::mpsc::TryRecvError;
21/// use std::time::Duration;
22///
23/// let stdin_stream = spawn_stdin_stream();
24///
25/// loop {
26/// match stdin_stream.try_recv() {
27/// Ok(line) => println!("Received: {}", line),
28/// Err(TryRecvError::Empty) => {
29/// // No input yet; continue execution
30/// }
31/// Err(TryRecvError::Disconnected) => {
32/// println!("Input stream closed. Exiting...");
33/// break;
34/// }
35/// }
36/// std::thread::sleep(Duration::from_millis(500));
37/// }
38/// ```
39pub fn spawn_stdin_stream() -> Receiver<String> {
40 let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();
41
42 thread::spawn(move || {
43 let stdin = io::stdin();
44 let mut stdin_lock = stdin.lock();
45
46 loop {
47 let mut buffer = String::new();
48 match stdin_lock.read_line(&mut buffer) {
49 Ok(0) => break, // EOF detected, exit thread
50 Ok(_) => {
51 if tx.send(buffer.trim().to_string()).is_err() {
52 break; // Exit if receiver is dropped
53 }
54 }
55 Err(_) => break, // Read failure
56 }
57 }
58 });
59
60 rx
61}
62
63/// Reads from stdin if available, otherwise returns a default value.
64///
65/// **Non-blocking:** This function polls `stdin` once and immediately returns.
66/// If no input is available within the polling time, it returns the provided default value.
67///
68/// # Arguments
69/// * `default` - An optional fallback value returned if no input is available.
70///
71/// # Returns
72/// * `Option<String>` - The trimmed `stdin` input as a `String` if available, or the provided `default` as a `String` if no input is received.
73///
74/// # Example
75/// ```
76/// use stdin_nonblocking::get_stdin_or_default;
77///
78/// let input = get_stdin_or_default(Some("fallback_value"));
79///
80/// assert_eq!(input, Some("fallback_value".to_string()));
81/// ```
82pub fn get_stdin_or_default(default: Option<&str>) -> Option<String> {
83 let stdin_channel = spawn_stdin_stream();
84 let mut input = String::new();
85
86 // Give the reader thread a short time to capture any available input
87 thread::sleep(Duration::from_millis(50));
88
89 while let Ok(line) = stdin_channel.try_recv() {
90 input.push_str(&line); // Collect all lines
91 input.push('\n'); // Add a newline between lines
92 }
93
94 // If input was collected, return it. Otherwise, return the default value.
95 if !input.trim().is_empty() {
96 Some(input.trim().to_string())
97 } else {
98 default.map(|s| s.to_string())
99 }
100}