mdcat/args.rs
1// Copyright 2018-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 clap::{ValueEnum, ValueHint};
8use clap_complete::Shell;
9
10/// Which colour theme to use for rendering.
11#[derive(Debug, Clone, Copy, Default, ValueEnum)]
12pub enum ThemeChoice {
13 /// Detect dark/light mode from the terminal and pick accordingly.
14 #[default]
15 Auto,
16 /// Use the built-in dark theme.
17 Dark,
18 /// Use the built-in light theme.
19 Light,
20 /// Catppuccin Mocha (dark).
21 #[value(name = "catppuccin-mocha")]
22 CatppuccinMocha,
23 /// Catppuccin Latte (light).
24 #[value(name = "catppuccin-latte")]
25 CatppuccinLatte,
26 /// Gruvbox dark.
27 #[value(name = "gruvbox-dark")]
28 GruvboxDark,
29 /// Gruvbox light.
30 #[value(name = "gruvbox-light")]
31 GruvboxLight,
32 /// Dracula.
33 Dracula,
34 /// Nord.
35 Nord,
36 /// Solarized dark.
37 #[value(name = "solarized-dark")]
38 SolarizedDark,
39 /// Solarized light.
40 #[value(name = "solarized-light")]
41 SolarizedLight,
42}
43
44/// Which inline image protocol to use, overriding auto-detection.
45#[derive(Debug, Clone, Copy, ValueEnum)]
46pub enum ImageProtocolChoice {
47 /// Disable inline images entirely.
48 None,
49 /// iTerm2's inline image protocol.
50 #[value(name = "iterm2")]
51 ITerm2,
52 /// The kitty terminal graphics protocol.
53 Kitty,
54 /// The sixel image protocol.
55 Sixel,
56}
57
58impl ImageProtocolChoice {
59 /// The image capability this choice maps to, or `None` to disable inline images.
60 pub fn to_image_capability(
61 self,
62 ) -> Option<pulldown_cmark_mdcat::terminal::capabilities::ImageCapability> {
63 use pulldown_cmark_mdcat::terminal::capabilities::{iterm2, kitty, sixel, ImageCapability};
64 match self {
65 ImageProtocolChoice::None => None,
66 ImageProtocolChoice::ITerm2 => Some(ImageCapability::ITerm2(iterm2::ITerm2Protocol)),
67 ImageProtocolChoice::Kitty => {
68 Some(ImageCapability::Kitty(kitty::KittyGraphicsProtocol))
69 }
70 ImageProtocolChoice::Sixel => Some(ImageCapability::Sixel(sixel::SixelProtocol)),
71 }
72 }
73}
74
75fn after_help() -> &'static str {
76 "See 'man 1 mdcat' for more information.
77
78mdcat can be installed as or linked to mdless,
79for automatic pagination, or to mdpick, to fuzzy-find
80a file with fzf before rendering it.
81
82Report issues to <https://github.com/BIRSAx2/mdcat>."
83}
84
85fn long_version() -> &'static str {
86 concat!(
87 env!("CARGO_PKG_VERSION"),
88 "
89Copyright (C) Sebastian Wiesner, Mouhieddine Sabir, and contributors
90
91This program is subject to the terms of the Mozilla Public License,
92v. 2.0. If a copy of the MPL was not distributed with this file,
93You can obtain one at http://mozilla.org/MPL/2.0/."
94 )
95}
96
97#[derive(Debug, clap::Parser)]
98#[command(multicall = true)]
99pub struct Args {
100 #[command(subcommand)]
101 pub command: Command,
102}
103
104#[derive(Debug, clap::Subcommand)]
105pub enum Command {
106 #[command(version, about, after_help = after_help(), long_version = long_version())]
107 Mdcat {
108 #[command(flatten)]
109 args: CommonArgs,
110 /// Paginate the output of mdcat with a pager like less (default for mdless).
111 #[arg(short, long, overrides_with = "no_pager")]
112 paginate: bool,
113 /// Do not paginate output (default). Overrides an earlier --paginate.
114 #[arg(short = 'P', long)]
115 no_pager: bool,
116 },
117 #[command(version, about, after_help = after_help(), long_version = long_version())]
118 Mdless {
119 #[command(flatten)]
120 args: CommonArgs,
121 /// Do not paginate output (default for mdcat).
122 #[arg(short = 'P', long, overrides_with = "paginate")]
123 no_pager: bool,
124 /// Paginate the output of mdcat with a pager like less (default). Overrides an earlier --no-pager.
125 #[arg(short, long)]
126 paginate: bool,
127 },
128 /// Fuzzy-find a Markdown file below the current directory with fzf, then render it.
129 ///
130 /// The FILENAMES argument, if given, is instead taken as the single directory to search
131 /// below (default: the current directory). Requires fzf <https://github.com/junegunn/fzf>.
132 #[command(version, about, after_help = after_help(), long_version = long_version())]
133 Mdpick {
134 #[command(flatten)]
135 args: CommonArgs,
136 /// Do not paginate output (default for mdcat).
137 #[arg(short = 'P', long, overrides_with = "paginate")]
138 no_pager: bool,
139 /// Paginate the output of mdcat with a pager like less (default). Overrides an earlier --no-pager.
140 #[arg(short, long)]
141 paginate: bool,
142 },
143}
144
145impl Command {
146 pub fn paginate(&self) -> bool {
147 match *self {
148 // In both cases look at the option indicating the non-default
149 // behaviour; the overrides above are configured accordingly.
150 Command::Mdcat { paginate, .. } => paginate,
151 Command::Mdless { no_pager, .. } => !no_pager,
152 Command::Mdpick { no_pager, .. } => !no_pager,
153 }
154 }
155}
156
157impl std::ops::Deref for Command {
158 type Target = CommonArgs;
159
160 fn deref(&self) -> &Self::Target {
161 match self {
162 Command::Mdcat { args, .. } => args,
163 Command::Mdless { args, .. } => args,
164 Command::Mdpick { args, .. } => args,
165 }
166 }
167}
168
169#[derive(Debug, clap::Args)]
170// #[command(author, version, about, after_help = after_help(), long_version = long_version())]
171pub struct CommonArgs {
172 /// Files to read. If - read from standard input instead.
173 #[arg(default_value="-", value_hint = ValueHint::FilePath)]
174 pub filenames: Vec<String>,
175 /// Disable all colours and other styles.
176 #[arg(short = 'c', long, aliases=["nocolour", "no-color", "nocolor"])]
177 pub no_colour: bool,
178 /// Maximum number of columns to use for output. Defaults to 80, the terminal width (whichever
179 /// is smaller), or `defaults.columns` in `~/.config/mdcat/config.toml`. Pass 0 to disable
180 /// line wrapping.
181 #[arg(long, conflicts_with = "full_width")]
182 pub columns: Option<u16>,
183 /// Use the full terminal width instead of capping it at 80 columns. Also settable via
184 /// `defaults.full_width` in `~/.config/mdcat/config.toml`.
185 #[arg(long, conflicts_with = "columns")]
186 pub full_width: bool,
187 /// Do not load remote resources like images. Also settable via `defaults.local_only` in
188 /// `~/.config/mdcat/config.toml`.
189 #[arg(short, long = "local")]
190 pub local_only: bool,
191 /// Exit immediately if any error occurs processing an input file. Also settable via
192 /// `defaults.fail_fast` in `~/.config/mdcat/config.toml`.
193 #[arg(long = "fail")]
194 pub fail_fast: bool,
195 /// Print detected terminal name and exit.
196 #[arg(long = "detect-terminal")]
197 pub detect_and_exit: bool,
198 /// Skip terminal detection and only use ANSI formatting.
199 #[arg(long = "ansi", conflicts_with = "no_colour")]
200 pub ansi_only: bool,
201 /// Generate completions for a shell to standard output and exit.
202 #[arg(long)]
203 pub completions: Option<Shell>,
204 /// Colour theme to use. Defaults to auto-detecting dark or light from the terminal, or to
205 /// the `[theme]` section's `base` in `~/.config/mdcat/config.toml` if that file exists.
206 #[arg(long, env = "MDCAT_THEME", value_name = "THEME")]
207 pub theme: Option<ThemeChoice>,
208 /// Watch the input file and re-render on change. Requires a single file argument.
209 #[arg(short, long)]
210 pub watch: bool,
211 /// Add a two-space left margin to all output. Reduces the effective render width accordingly.
212 /// Also settable via `defaults.margin` in `~/.config/mdcat/config.toml`.
213 #[arg(long)]
214 pub margin: bool,
215 /// Render typographic punctuation: straight quotes become curly, `--`/`---` become en/em
216 /// dashes, and `...` becomes an ellipsis. Also settable via `defaults.smart_punctuation` in
217 /// `~/.config/mdcat/config.toml`.
218 #[arg(long)]
219 pub smart_punctuation: bool,
220 /// Print a sample rendered with every built-in theme, to help pick one, and exit.
221 #[arg(long)]
222 pub list_themes: bool,
223 /// Print a table of contents generated from the document's headings before its content.
224 /// Entries link to the source file, for terminals and later viewers that support OSC 8
225 /// links and resolve GitHub-style heading anchors; plain text on standard input, since
226 /// there's no file to link to.
227 #[arg(long)]
228 pub toc: bool,
229 /// Force a specific inline image protocol instead of auto-detecting one from the terminal.
230 /// Useful inside tmux/screen, where the outer terminal's capabilities usually aren't visible
231 /// to auto-detection. `none` disables inline images entirely. Also settable via
232 /// `$MDCAT_IMAGE_PROTOCOL`.
233 #[arg(long, env = "MDCAT_IMAGE_PROTOCOL", value_name = "PROTOCOL")]
234 pub image_protocol: Option<ImageProtocolChoice>,
235 /// Expand tabs in the input to spaces, using a tab stop width of COLUMNS, before parsing.
236 /// Off by default, so literal tabs pass through unchanged; without this, a tab inside text
237 /// content (not part of the Markdown block structure) throws off line-wrapping and alignment
238 /// width calculations, since terminals render it as jumping to the next tab stop rather than
239 /// occupying a single column. Also settable via `defaults.tabs` in
240 /// `~/.config/mdcat/config.toml`.
241 #[arg(long, value_name = "COLUMNS")]
242 pub tabs: Option<u16>,
243}
244
245/// What resources mdcat may access.
246#[derive(Debug, Copy, Clone)]
247pub enum ResourceAccess {
248 /// Only allow local resources.
249 LocalOnly,
250 /// Allow remote resources
251 Remote,
252}
253
254impl CommonArgs {
255 /// Whether remote resource access is permitted.
256 pub fn resource_access(&self) -> ResourceAccess {
257 if self.local_only {
258 ResourceAccess::LocalOnly
259 } else {
260 ResourceAccess::Remote
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::Args;
268 use clap::CommandFactory;
269
270 #[test]
271 fn verify_app() {
272 Args::command().debug_assert();
273 }
274}