1#![doc(html_root_url = "https://docs.rs/pager/0.16.1")]
80#![cfg_attr(feature = "pedantic", warn(clippy::pedantic))]
81#![warn(clippy::use_self)]
82#![warn(deprecated_in_future)]
83#![warn(future_incompatible)]
84#![warn(unreachable_pub)]
85#![warn(missing_debug_implementations)]
86#![warn(rust_2018_compatibility)]
87#![warn(rust_2018_idioms)]
88#![warn(unused)]
89#![deny(warnings)]
90
91mod utils;
92
93use std::env;
94use std::ffi::{OsStr, OsString};
95
96const DEFAULT_PAGER_ENV: &str = "PAGER";
98
99const NOPAGER_ENV: &str = "NOPAGER";
101
102const DEFAULT_PAGER: &str = "more";
104
105#[derive(Debug)]
107pub struct Pager {
108 default_pager: Option<OsString>,
109 pager: Option<OsString>,
110 envs: Vec<OsString>,
111 on: bool,
112 skip_on_notty: bool,
113}
114
115impl Default for Pager {
116 fn default() -> Self {
117 Self {
118 default_pager: None,
119 pager: env::var_os(DEFAULT_PAGER_ENV),
120 envs: Vec::new(),
121 on: true,
122 skip_on_notty: true,
123 }
124 }
125}
126
127impl Pager {
128 pub fn new() -> Self {
130 Self::default()
131 }
132
133 pub fn with_env(env: &str) -> Self {
135 Self {
136 pager: env::var_os(env),
137 ..Self::default()
138 }
139 }
140
141 #[deprecated(since = "0.12.0", note = "use with_env() instead")]
142 pub fn env(env: &str) -> Self {
143 Self::with_env(env)
144 }
145
146 pub fn with_default_pager<S>(pager: S) -> Self
148 where
149 S: Into<OsString>,
150 {
151 let default_pager = Some(pager.into());
152 Self {
153 default_pager,
154 ..Self::default()
155 }
156 }
157
158 pub fn with_pager(pager: &str) -> Self {
160 Self {
161 pager: Some(pager.into()),
162 ..Self::default()
163 }
164 }
165
166 pub fn pager_envs(self, envs: impl IntoIterator<Item = impl Into<OsString>>) -> Self {
168 let envs = envs.into_iter().map(|s| s.into()).collect();
169 Self { envs, ..self }
170 }
171
172 #[deprecated(since = "0.14.0", note = "'skip_on_notty' is default now")]
174 pub fn skip_on_notty(self) -> Self {
175 Self {
176 skip_on_notty: true,
177 ..self
178 }
179 }
180
181 pub fn is_on(&self) -> bool {
183 self.on
184 }
185
186 fn pager(&self) -> Option<OsString> {
187 let fallback_pager = || Some(OsStr::new(DEFAULT_PAGER).into());
188
189 if env::var_os(NOPAGER_ENV).is_some() {
190 None
191 } else {
192 self.pager
193 .clone()
194 .or_else(|| self.default_pager.clone())
195 .or_else(fallback_pager)
196 }
197 }
198
199 pub fn setup(&mut self) {
202 if self.skip_on_notty && !utils::isatty(libc::STDOUT_FILENO) {
203 self.on = false;
204 return;
205 }
206 if let Some(ref pager) = self.pager() {
207 let (pager_stdin, main_stdout) = utils::pipe();
208 let pid = utils::fork();
209 match pid {
210 -1 => {
211 utils::close(pager_stdin);
213 utils::close(main_stdout);
214 self.on = false
215 }
216 0 => {
217 utils::dup2(main_stdout, libc::STDOUT_FILENO);
219 utils::close(pager_stdin);
220 }
221 _ => {
222 utils::dup2(pager_stdin, libc::STDIN_FILENO);
224 utils::close(main_stdout);
225 utils::execvpe(pager, &self.envs);
226 }
227 }
228 } else {
229 self.on = false;
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use std::ops::Drop;
238
239 enum PagerEnv {
240 Reinstate(OsString, OsString),
241 Remove(OsString),
242 }
243
244 impl PagerEnv {
245 fn new<S: AsRef<OsStr>>(env: S) -> Self {
246 let env = env.as_ref().into();
247 if let Some(value) = env::var_os(&env) {
248 Self::Reinstate(env, value)
249 } else {
250 Self::Remove(env)
251 }
252 }
253
254 fn set<S: AsRef<OsStr>>(&self, value: S) {
255 match self {
256 Self::Reinstate(env, _) | Self::Remove(env) => env::set_var(env, value),
257 }
258 }
259
260 fn remove(&self) {
261 match self {
262 Self::Reinstate(env, _) | Self::Remove(env) => env::remove_var(env),
263 }
264 }
265 }
266
267 impl Drop for PagerEnv {
268 fn drop(&mut self) {
269 match self {
270 Self::Reinstate(env, value) => env::set_var(env, value),
271 Self::Remove(env) => env::remove_var(env),
272 }
273 }
274 }
275
276 fn assert_pager(pager: &Pager, result: &str) {
277 assert_eq!(pager.pager(), Some(OsStr::new(result).into()));
278 }
279
280 #[test]
281 fn nopager() {
282 let nopager = PagerEnv::new(NOPAGER_ENV);
283 nopager.set("");
284
285 let pager = Pager::new();
286 assert!(pager.pager().is_none());
287 }
288
289 #[test]
290 fn fallback_uses_more() {
291 let pager = Pager::new();
292 assert_pager(&pager, DEFAULT_PAGER);
293 }
294
295 #[test]
296 fn with_default_pager_without_env() {
297 let pagerenv = PagerEnv::new(DEFAULT_PAGER_ENV);
298 pagerenv.remove();
299
300 let pager = Pager::with_default_pager("more_or_less");
301 assert_pager(&pager, "more_or_less");
302 }
303
304 #[test]
305 fn with_default_pager_with_env() {
306 let pagerenv = PagerEnv::new(DEFAULT_PAGER_ENV);
307 pagerenv.set("something_else");
308
309 let pager = Pager::with_default_pager("more_or_less");
310 assert_pager(&pager, "something_else");
311 }
312
313 #[test]
314 fn with_default_pager() {
315 let pager = Pager::with_default_pager("more_or_less");
316 assert_pager(&pager, "more_or_less");
317 }
318
319 #[test]
320 fn with_pager() {
321 let pager = Pager::with_pager("now_or_never");
322 assert_pager(&pager, "now_or_never");
323 }
324}