use std::ffi::OsString;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use super::less::retrieve_less_version;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Could not parse pager command")]
ParseError(String),
#[error("Could not open STDIN for pager")]
PagerError,
}
pub enum OutputType {
Pager(Child),
Stdout(io::Stdout),
}
impl OutputType {
pub fn for_pager(pager: Option<String>, quit_if_one_screen: bool) -> Result<Self, Error> {
let replace_arguments_to_less = pager.is_none();
let pager = pager.unwrap_or_else(|| String::from("less"));
let pagerflags = match shell_words::split(&pager) {
Ok(pagerflags) => pagerflags,
Err(_) => return Err(Error::ParseError(pager)),
};
Ok(match pagerflags.split_first() {
Some((pager_name, args)) => {
let pager_path = PathBuf::from(pager_name);
let is_less = pager_path.file_stem() == Some(&OsString::from("less"));
let process = if is_less {
Self::make_process_from_less_path(
pager_path,
args,
replace_arguments_to_less,
quit_if_one_screen,
)
} else {
Self::make_process_from_pager_path(pager_path, args)
};
if let Some(mut process) = process {
process
.stdin(Stdio::piped())
.spawn()
.map(OutputType::Pager)
.unwrap_or_else(|_| OutputType::stdout())
} else {
OutputType::stdout()
}
}
None => OutputType::stdout(),
})
}
pub fn stdout() -> Self {
OutputType::Stdout(io::stdout())
}
pub fn handle(&mut self) -> Result<&mut dyn Write, crate::output::Error> {
match *self {
OutputType::Pager(ref mut command) => match command.stdin.as_mut() {
Some(stdin) => Ok(stdin),
None => Err(Error::PagerError),
},
OutputType::Stdout(ref mut handle) => Ok(handle),
}
}
fn make_process_from_less_path(
less_path: PathBuf,
args: &[String],
replace_arguments_to_less: bool,
quit_if_one_screen: bool,
) -> Option<Command> {
if let Ok(less_path) = grep_cli::resolve_binary(less_path) {
let mut p = Command::new(&less_path);
if args.is_empty() || replace_arguments_to_less {
p.args(vec!["--RAW-CONTROL-CHARS"]);
match retrieve_less_version() {
None => {
p.arg("--no-init");
}
Some(version) if (version < 530 || (cfg!(windows) && version < 558)) => {
p.arg("--no-init");
}
_ => {}
}
if quit_if_one_screen {
p.arg("--quit-if-one-screen");
}
} else {
p.args(args);
}
p.env("LESSCHARSET", "UTF-8");
p.env("LESSANSIENDCHARS", "mK");
Some(p)
} else {
None
}
}
fn make_process_from_pager_path(pager_path: PathBuf, args: &[String]) -> Option<Command> {
if pager_path.file_stem() == Some(&OsString::from("rep")) {
eprintln!(
"Error: rep is set as the pager, which would cause infinite recursion. \
Falling back to stdout."
);
return None;
}
if let Ok(pager_path) = grep_cli::resolve_binary(pager_path) {
let mut p = Command::new(&pager_path);
p.args(args);
Some(p)
} else {
None
}
}
}
impl Drop for OutputType {
fn drop(&mut self) {
if let OutputType::Pager(ref mut command) = *self {
let _ = command.wait();
}
}
}