Skip to main content

mdcat/
output.rs

1// Copyright 2020 Sebastian Wiesner <sebastian@swsnr.de>
2
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use std::io::Write;
8use std::process::*;
9
10use anyhow::{bail, Context, Result};
11use tracing::{event, Level};
12
13/// The output for mdcat
14pub enum Output {
15    /// Standard output
16    Stdout(std::io::Stdout),
17    /// A pager
18    Pager(Child),
19}
20
21impl Drop for Output {
22    /// Drop the output.
23    ///
24    /// When outputting to a pager wait for the pager to exit.
25    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            // Continue looking
48            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    /// Get the writer to write to the output.
65    ///
66    /// When outputting to a pager returns the stdin handle to the pager.
67    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    /// Create a new output.
75    ///
76    /// If `try_paginate` is `true` try to output to a pager.  If stdout is not a TTY, that is, if
77    /// there's no terminal to paginate on, print to stdout nonetheless.
78    ///
79    /// Take the pager command from `$MDCAT_PAGER` or `$PAGER`, and default to `less -R` if both are
80    /// unset.  If any of the variables is empty use stdout (assuming that the user
81    /// wanted to disabled paging explicitly).
82    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}