use std::io::Write;
use std::process::*;
use anyhow::{bail, Context, Result};
use tracing::{event, Level};
pub enum Output {
Stdout(std::io::Stdout),
Pager(Child),
}
impl Drop for Output {
fn drop(&mut self) {
if let Output::Pager(ref mut child) = *self {
let _ = child.wait();
}
}
}
fn parse_env_var(name: &str) -> Result<Option<Vec<String>>> {
use std::env::VarError;
match std::env::var(name) {
Ok(value) => shell_words::split(&value)
.with_context(|| format!("Failed to parse value {} of {}", &value, &name))
.map(Some),
Err(VarError::NotPresent) => Ok(None),
Err(VarError::NotUnicode(value)) => bail!("Value of {} not unicode: {:?}", name, value),
}
}
fn pager_from_env() -> Result<Vec<String>> {
for envvar in ["MDCAT_PAGER", "PAGER"] {
event!(Level::TRACE, envvar, "looking for pager in environment");
match parse_env_var(envvar) {
Ok(None) => {}
Ok(Some(command)) => {
event!(Level::INFO, envvar, "Using {:?} as pager", command);
return Ok(command);
}
Err(error) => {
event!(Level::ERROR, envvar, %error, "Parsing failed: {:#}", error);
return Err(error);
}
}
}
event!(Level::DEBUG, "Falling back to default pager less -r");
Ok(vec!["less".into(), "-r".into()])
}
impl Output {
pub fn writer(&mut self) -> &mut dyn Write {
match self {
Output::Stdout(handle) => handle,
Output::Pager(child) => child.stdin.as_mut().unwrap(),
}
}
pub fn new(try_paginate: bool) -> Result<Output> {
if try_paginate {
match pager_from_env()?.split_first() {
None => {
event!(
Level::WARN,
"Empty pager command, falling back to standard output"
);
Ok(Output::Stdout(std::io::stdout()))
}
Some((command, args)) => {
event!(
Level::TRACE,
"Paginating with command {}, args {:?}",
command,
args
);
Command::new(command)
.args(args)
.stdin(Stdio::piped())
.spawn()
.with_context(|| {
format!("Failed to spawn pager {command} with args {args:?}")
})
.map(Output::Pager)
}
}
} else {
Ok(Output::Stdout(std::io::stdout()))
}
}
}