use std::io::{self, IsTerminal, Write};
use std::process::{Child, ChildStdin, Command, Stdio};
use log::warn;
pub struct PagedWriter {
target: Target,
}
enum Target {
Stdout(io::Stdout),
Deferred,
Pager { child: Child, stdin: ChildStdin },
}
impl PagedWriter {
pub fn new(use_pager: bool) -> Self {
let target = if use_pager && io::stdout().is_terminal() {
Target::Deferred
} else {
Target::Stdout(io::stdout())
};
PagedWriter { target }
}
pub fn finish(self) -> io::Result<()> {
if let Target::Pager { mut child, stdin } = self.target {
drop(stdin);
match child.wait() {
Ok(status) if !status.success() => warn!("pager exited with {status}"),
Ok(_) => {}
Err(e) => warn!("waiting for the pager failed: {e}"),
}
}
Ok(())
}
fn sink(&mut self) -> &mut dyn Write {
if matches!(self.target, Target::Deferred) {
self.target = spawn();
}
match self.target {
Target::Stdout(ref mut out) => out,
Target::Pager { ref mut stdin, .. } => stdin,
Target::Deferred => unreachable!("spawn resolves the deferred target"),
}
}
}
fn spawn() -> Target {
let pager = std::env::var("FSLOG_PAGER").unwrap_or_else(|_| "less".to_string());
let mut parts = pager.split_whitespace();
let Some(program) = parts.next() else {
return Target::Stdout(io::stdout());
};
let args: Vec<&str> = parts.collect();
let default_args = ["-RFX"];
let args = if args.is_empty() && program == "less" {
&default_args[..]
} else {
&args[..]
};
match Command::new(program)
.args(args)
.stdin(Stdio::piped())
.spawn()
{
Ok(mut child) => match child.stdin.take() {
Some(stdin) => Target::Pager { child, stdin },
None => {
warn!("pager {program} has no stdin pipe; output goes to stdout");
Target::Stdout(io::stdout())
}
},
Err(e) => {
warn!("failed to spawn pager {program}: {e}; output goes to stdout");
Target::Stdout(io::stdout())
}
}
}
impl Write for PagedWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.sink().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
match self.target {
Target::Deferred => Ok(()),
Target::Stdout(ref mut out) => out.flush(),
Target::Pager { ref mut stdin, .. } => stdin.flush(),
}
}
}
pub fn is_broken_pipe(err: &io::Error) -> bool {
err.kind() == io::ErrorKind::BrokenPipe
}