use std::fs::File;
use std::io::{BufRead, IsTerminal};
use std::sync::mpsc;
use std::time::Duration;
const MAX_SAMPLE: usize = 500;
const PIPE_TIMEOUT: Duration = Duration::from_secs(2);
pub struct Live {
pub leftover: Vec<u8>,
pub pipe: File,
}
fn read_sample(r: &mut impl BufRead) -> String {
let mut out = String::new();
let mut line = String::new();
for _ in 0..MAX_SAMPLE {
line.clear();
match r.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {
let trimmed = line.trim_end_matches(['\n', '\r']);
if !trimmed.trim().is_empty() {
out.push_str(trimmed);
out.push('\n');
}
}
Err(_) => break,
}
}
out
}
pub fn from_file(path: &str) -> std::io::Result<String> {
let f = File::open(path)?;
Ok(read_sample(&mut std::io::BufReader::new(f)))
}
fn timeout_exit() -> ! {
eprintln!(
"jlf it: timed out reading a sample from stdin. If it's a live stream \
(e.g. `tail -f`), pass a finite sample instead — `jlf it <file>` or \
`head -100 logs | jlf it`."
);
std::process::exit(1);
}
#[cfg(unix)]
pub fn from_stdin_if_piped() -> Option<(String, Option<Live>)> {
use std::os::unix::io::FromRawFd;
if std::io::stdin().is_terminal() {
return None;
}
let raw = unsafe { libc_dup(0) };
if raw < 0 {
return Some((sample_stdin_only(), None));
}
let file = unsafe { File::from_raw_fd(raw) };
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut br = std::io::BufReader::new(file);
let sample = read_sample(&mut br);
let leftover = br.buffer().to_vec();
let pipe = br.into_inner();
let _ = tx.send((sample, leftover, pipe));
});
match rx.recv_timeout(PIPE_TIMEOUT) {
Ok((sample, leftover, pipe)) => {
reattach_tty();
Some((sample, Some(Live { leftover, pipe })))
}
Err(_) => timeout_exit(),
}
}
fn sample_stdin_only() -> String {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut br = std::io::BufReader::new(std::io::stdin().lock());
let _ = tx.send(read_sample(&mut br));
});
match rx.recv_timeout(PIPE_TIMEOUT) {
Ok(s) => s,
Err(_) => timeout_exit(),
}
}
#[cfg(not(unix))]
pub fn from_stdin_if_piped() -> Option<(String, Option<Live>)> {
if std::io::stdin().is_terminal() {
return None;
}
Some((sample_stdin_only(), None))
}
#[cfg(unix)]
fn reattach_tty() {
use std::os::unix::io::AsRawFd;
if let Ok(tty) = File::open("/dev/tty") {
unsafe {
libc_dup2(tty.as_raw_fd(), 0);
}
std::mem::forget(tty);
}
}
#[cfg(unix)]
extern "C" {
#[link_name = "dup"]
fn libc_dup(oldfd: i32) -> i32;
#[link_name = "dup2"]
fn libc_dup2(oldfd: i32, newfd: i32) -> i32;
}