1use std::io::Write;
8use std::process::*;
9
10use anyhow::{bail, Context, Result};
11use tracing::{event, Level};
12
13pub enum Output {
15 Stdout(std::io::Stdout),
17 Pager(Child),
19}
20
21impl Drop for Output {
22 fn drop(&mut self) {
26 if let Output::Pager(ref mut child) = *self {
27 let _ = child.wait();
28 }
29 }
30}
31
32fn parse_env_var(name: &str) -> Result<Option<Vec<String>>> {
33 use std::env::VarError;
34 match std::env::var(name) {
35 Ok(value) => shell_words::split(&value)
36 .with_context(|| format!("Failed to parse value {} of {}", &value, &name))
37 .map(Some),
38 Err(VarError::NotPresent) => Ok(None),
39 Err(VarError::NotUnicode(value)) => bail!("Value of {} not unicode: {:?}", name, value),
40 }
41}
42
43fn pager_from_env() -> Result<Vec<String>> {
44 for envvar in ["MDCAT_PAGER", "PAGER"] {
45 event!(Level::TRACE, envvar, "looking for pager in environment");
46 match parse_env_var(envvar) {
47 Ok(None) => {}
49 Ok(Some(command)) => {
50 event!(Level::INFO, envvar, "Using {:?} as pager", command);
51 return Ok(command);
52 }
53 Err(error) => {
54 event!(Level::ERROR, envvar, %error, "Parsing failed: {:#}", error);
55 return Err(error);
56 }
57 }
58 }
59 event!(Level::DEBUG, "Falling back to default pager less -r");
60 Ok(vec!["less".into(), "-r".into()])
61}
62
63impl Output {
64 pub fn writer(&mut self) -> &mut dyn Write {
68 match self {
69 Output::Stdout(handle) => handle,
70 Output::Pager(child) => child.stdin.as_mut().unwrap(),
71 }
72 }
73
74 pub fn new(try_paginate: bool) -> Result<Output> {
83 if try_paginate {
84 match pager_from_env()?.split_first() {
85 None => {
86 event!(
87 Level::WARN,
88 "Empty pager command, falling back to standard output"
89 );
90 Ok(Output::Stdout(std::io::stdout()))
91 }
92 Some((command, args)) => {
93 event!(
94 Level::TRACE,
95 "Paginating with command {}, args {:?}",
96 command,
97 args
98 );
99 Command::new(command)
100 .args(args)
101 .stdin(Stdio::piped())
102 .spawn()
103 .with_context(|| {
104 format!("Failed to spawn pager {command} with args {args:?}")
105 })
106 .map(Output::Pager)
107 }
108 }
109 } else {
110 Ok(Output::Stdout(std::io::stdout()))
111 }
112 }
113}