1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright 2020 Sebastian Wiesner <sebastian@swsnr.de>
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::io::Write;
use std::process::*;
use anyhow::{bail, Context, Result};
use tracing::{event, Level};
/// The output for mdcat
pub enum Output {
/// Standard output
Stdout(std::io::Stdout),
/// A pager
Pager(Child),
}
impl Drop for Output {
/// Drop the output.
///
/// When outputting to a pager wait for the pager to exit.
fn drop(&mut self) {
if let Output::Pager(ref mut child) = *self {
let _ = child.wait();
}
}
}
fn parse_env_var(name: &str) -> Result<Option<Vec<String>>> {
use std::env::VarError;
match std::env::var(name) {
Ok(value) => shell_words::split(&value)
.with_context(|| format!("Failed to parse value {} of {}", &value, &name))
.map(Some),
Err(VarError::NotPresent) => Ok(None),
Err(VarError::NotUnicode(value)) => bail!("Value of {} not unicode: {:?}", name, value),
}
}
fn pager_from_env() -> Result<Vec<String>> {
for envvar in ["MDCAT_PAGER", "PAGER"] {
event!(Level::TRACE, envvar, "looking for pager in environment");
match parse_env_var(envvar) {
// Continue looking
Ok(None) => {}
Ok(Some(command)) => {
event!(Level::INFO, envvar, "Using {:?} as pager", command);
return Ok(command);
}
Err(error) => {
event!(Level::ERROR, envvar, %error, "Parsing failed: {:#}", error);
return Err(error);
}
}
}
event!(Level::DEBUG, "Falling back to default pager less -r");
Ok(vec!["less".into(), "-r".into()])
}
impl Output {
/// Get the writer to write to the output.
///
/// When outputting to a pager returns the stdin handle to the pager.
pub fn writer(&mut self) -> &mut dyn Write {
match self {
Output::Stdout(handle) => handle,
Output::Pager(child) => child.stdin.as_mut().unwrap(),
}
}
/// Create a new output.
///
/// If `try_paginate` is `true` try to output to a pager. If stdout is not a TTY, that is, if
/// there's no terminal to paginate on, print to stdout nonetheless.
///
/// Take the pager command from `$MDCAT_PAGER` or `$PAGER`, and default to `less -R` if both are
/// unset. If any of the variables is empty use stdout (assuming that the user
/// wanted to disabled paging explicitly).
pub fn new(try_paginate: bool) -> Result<Output> {
if try_paginate {
match pager_from_env()?.split_first() {
None => {
event!(
Level::WARN,
"Empty pager command, falling back to standard output"
);
Ok(Output::Stdout(std::io::stdout()))
}
Some((command, args)) => {
event!(
Level::TRACE,
"Paginating with command {}, args {:?}",
command,
args
);
Command::new(command)
.args(args)
.stdin(Stdio::piped())
.spawn()
.with_context(|| {
format!("Failed to spawn pager {command} with args {args:?}")
})
.map(Output::Pager)
}
}
} else {
Ok(Output::Stdout(std::io::stdout()))
}
}
}