Skip to main content

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}