#![doc(html_root_url = "https://docs.rs/pager/0.16.1")]
#![cfg_attr(feature = "pedantic", warn(clippy::pedantic))]
#![warn(clippy::use_self)]
#![warn(deprecated_in_future)]
#![warn(future_incompatible)]
#![warn(unreachable_pub)]
#![warn(missing_debug_implementations)]
#![warn(rust_2018_compatibility)]
#![warn(rust_2018_idioms)]
#![warn(unused)]
#![deny(warnings)]
mod utils;
use std::env;
use std::ffi::{OsStr, OsString};
const DEFAULT_PAGER_ENV: &str = "PAGER";
const NOPAGER_ENV: &str = "NOPAGER";
const DEFAULT_PAGER: &str = "more";
#[derive(Debug)]
pub struct Pager {
default_pager: Option<OsString>,
pager: Option<OsString>,
envs: Vec<OsString>,
on: bool,
skip_on_notty: bool,
}
impl Default for Pager {
fn default() -> Self {
Self {
default_pager: None,
pager: env::var_os(DEFAULT_PAGER_ENV),
envs: Vec::new(),
on: true,
skip_on_notty: true,
}
}
}
impl Pager {
pub fn new() -> Self {
Self::default()
}
pub fn with_env(env: &str) -> Self {
Self {
pager: env::var_os(env),
..Self::default()
}
}
#[deprecated(since = "0.12.0", note = "use with_env() instead")]
pub fn env(env: &str) -> Self {
Self::with_env(env)
}
pub fn with_default_pager<S>(pager: S) -> Self
where
S: Into<OsString>,
{
let default_pager = Some(pager.into());
Self {
default_pager,
..Self::default()
}
}
pub fn with_pager(pager: &str) -> Self {
Self {
pager: Some(pager.into()),
..Self::default()
}
}
pub fn pager_envs(self, envs: impl IntoIterator<Item = impl Into<OsString>>) -> Self {
let envs = envs.into_iter().map(|s| s.into()).collect();
Self { envs, ..self }
}
#[deprecated(since = "0.14.0", note = "'skip_on_notty' is default now")]
pub fn skip_on_notty(self) -> Self {
Self {
skip_on_notty: true,
..self
}
}
pub fn is_on(&self) -> bool {
self.on
}
fn pager(&self) -> Option<OsString> {
let fallback_pager = || Some(OsStr::new(DEFAULT_PAGER).into());
if env::var_os(NOPAGER_ENV).is_some() {
None
} else {
self.pager
.clone()
.or_else(|| self.default_pager.clone())
.or_else(fallback_pager)
}
}
pub fn setup(&mut self) {
if self.skip_on_notty && !utils::isatty(libc::STDOUT_FILENO) {
self.on = false;
return;
}
if let Some(ref pager) = self.pager() {
let (pager_stdin, main_stdout) = utils::pipe();
let pid = utils::fork();
match pid {
-1 => {
utils::close(pager_stdin);
utils::close(main_stdout);
self.on = false
}
0 => {
utils::dup2(main_stdout, libc::STDOUT_FILENO);
utils::close(pager_stdin);
}
_ => {
utils::dup2(pager_stdin, libc::STDIN_FILENO);
utils::close(main_stdout);
utils::execvpe(pager, &self.envs);
}
}
} else {
self.on = false;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Drop;
enum PagerEnv {
Reinstate(OsString, OsString),
Remove(OsString),
}
impl PagerEnv {
fn new<S: AsRef<OsStr>>(env: S) -> Self {
let env = env.as_ref().into();
if let Some(value) = env::var_os(&env) {
Self::Reinstate(env, value)
} else {
Self::Remove(env)
}
}
fn set<S: AsRef<OsStr>>(&self, value: S) {
match self {
Self::Reinstate(env, _) | Self::Remove(env) => env::set_var(env, value),
}
}
fn remove(&self) {
match self {
Self::Reinstate(env, _) | Self::Remove(env) => env::remove_var(env),
}
}
}
impl Drop for PagerEnv {
fn drop(&mut self) {
match self {
Self::Reinstate(env, value) => env::set_var(env, value),
Self::Remove(env) => env::remove_var(env),
}
}
}
fn assert_pager(pager: &Pager, result: &str) {
assert_eq!(pager.pager(), Some(OsStr::new(result).into()));
}
#[test]
fn nopager() {
let nopager = PagerEnv::new(NOPAGER_ENV);
nopager.set("");
let pager = Pager::new();
assert!(pager.pager().is_none());
}
#[test]
fn fallback_uses_more() {
let pager = Pager::new();
assert_pager(&pager, DEFAULT_PAGER);
}
#[test]
fn with_default_pager_without_env() {
let pagerenv = PagerEnv::new(DEFAULT_PAGER_ENV);
pagerenv.remove();
let pager = Pager::with_default_pager("more_or_less");
assert_pager(&pager, "more_or_less");
}
#[test]
fn with_default_pager_with_env() {
let pagerenv = PagerEnv::new(DEFAULT_PAGER_ENV);
pagerenv.set("something_else");
let pager = Pager::with_default_pager("more_or_less");
assert_pager(&pager, "something_else");
}
#[test]
fn with_default_pager() {
let pager = Pager::with_default_pager("more_or_less");
assert_pager(&pager, "more_or_less");
}
#[test]
fn with_pager() {
let pager = Pager::with_pager("now_or_never");
assert_pager(&pager, "now_or_never");
}
}