use atty::Stream;
use std::process::{ExitStatus, Stdio};
use std::{env, io, result};
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
pub type EnvVar = (String, String);
#[derive(Debug)]
pub struct Pager {
pager_env: PagerEnv,
}
impl Pager {
pub fn new(pager_env: PagerEnv) -> Self {
Self { pager_env }
}
pub fn command(&self) -> String {
self.pager_env.pager()
}
pub fn env(&self) -> Option<EnvVar> {
self.pager_env.pager_env()
}
pub fn is_cat(&self) -> bool {
self.command() == "cat" || self.command().ends_with("/cat")
}
pub fn is_tty(&self) -> bool {
atty::is(Stream::Stdout)
}
pub async fn page_to_pager_with_error(&self, output: &str) -> io::Result<ExitStatus> {
let mut command = Command::new(&self.command());
if let Some((key, value)) = &self.env() {
command.env(key, value);
}
let mut command = command.stdin(Stdio::piped()).spawn().map_err(|e| {
let message = format!("failed to spawn pager: {e}");
io::Error::new(io::ErrorKind::Other, message)
})?;
if let Some(mut stdin) = command.stdin.take() {
stdin.write_all(output.as_bytes()).await?;
}
command.wait().await
}
pub async fn page_to_stdout_with_error(&self, output: &str) -> io::Result<ExitStatus> {
println!("{}", output);
Ok(ExitStatus::default())
}
pub async fn page_with_error(&self, output: &str) -> io::Result<ExitStatus> {
if self.is_cat() || !self.is_tty() {
self.page_to_stdout_with_error(output).await
} else {
self.page_to_pager_with_error(output).await
}
}
pub async fn page(&self, output: &str) -> Result {
let status = self
.page_with_error(output)
.await
.map_err(|e| format!("I/O error: {e}"))?;
if status.success() {
Ok(())
} else {
let message = format!("pager {} exited with status {status}", self.command());
Err(message)
}
}
}
pub type Result = result::Result<(), String>;
#[derive(Debug, Default)]
pub struct PagerEnv {
oneline: bool,
}
impl PagerEnv {
pub const DEFAULT_PAGER: &'static str = "/usr/bin/less";
pub fn oneline(self, oneline: bool) -> Self {
Self { oneline }
}
pub fn pager(&self) -> String {
env::var("PAGER").unwrap_or(String::from(PagerEnv::DEFAULT_PAGER))
}
pub fn pager_env(&self) -> Option<EnvVar> {
let less = env::var_os("LESS").unwrap_or(
"FSRX"
.parse()
.expect("could not parse 'FSRX' into OsString"),
);
let less = less.to_string_lossy();
let less = if !less.contains("R") {
less + "R"
} else {
less
};
let less = if self.oneline && !less.contains("S") {
less + "S"
} else {
less
};
Some((String::from("LESS"), less.to_string()))
}
}