use clap::{
Parser, ValueEnum,
builder::{
Styles,
styling::{AnsiColor, Effects},
},
};
use clap_complete::Shell;
use rasteroid::{
RasterEncoder,
term_misc::{self, EnvIdentifiers, Wininfo},
};
use tracing::debug;
#[derive(Parser)]
#[command(
name = "mcat",
version,
author,
about = "Terminal image, video, and Markdown viewer",
color = clap::ColorChoice::Always,
styles = get_styles(),
)]
#[derive(Clone)]
pub struct McatConfig {
#[arg(num_args = 1.., required_unless_present_any = ["report", "generate", "fetch_chromium", "fetch_ffmpeg", "fetch_clean", "stdin_piped"])]
pub input: Vec<String>,
#[arg(long, hide = true, env = "MCAT_STDIN_PIPED")]
stdin_piped: bool,
#[arg(
long,
short = 't',
help_heading = "Core Options",
env = "MCAT_THEME",
default_value_t
)]
pub theme: Theme,
#[arg(
long,
short = 'o',
value_name = "format",
help_heading = "Core Options",
default_value_if("inline", "true", "inline"),
default_value_if("interactive", "true", "interactive")
)]
pub output: Option<OutputFormat>,
#[arg(short = 'i', help_heading = "Core Options")]
inline: bool,
#[arg(short = 'I', help_heading = "Core Options")]
interactive: bool,
#[arg(long, help_heading = "Core Options")]
pub report: bool,
#[arg(long, help_heading = "Core Options")]
pub silent: bool,
#[arg(
long,
value_name = "WxH",
help_heading = "Core Options",
default_value = "autoxauto"
)]
pub spx: String,
#[arg(
long,
value_name = "WxH",
help_heading = "Core Options",
default_value = "autoxauto"
)]
pub sc: String,
#[arg(
long,
value_name = "float",
help_heading = "Core Options",
default_value_t = 1.0
)]
pub scalex: f32,
#[arg(
long,
value_name = "float",
help_heading = "Core Options",
default_value_t = 1.0
)]
pub scaley: f32,
#[arg(long, help_heading = "Markdown Viewing")]
pub no_linenumbers: bool,
#[arg(long = "md-image", value_name = "mode", help_heading = "Markdown Viewing",
default_value_t = MdImageMode::Auto,
default_value_if("fast", "true", "none"),
env = "MCAT_MD_IMAGE")]
pub md_image: MdImageMode,
#[arg(short = 'f', help_heading = "Markdown Viewing")]
fast: bool,
#[arg(long, help_heading = "Markdown Viewing")]
pub force_embed_images: bool,
#[arg(long, help_heading = "Markdown Viewing")]
pub header: bool,
#[arg(long, help_heading = "Markdown Viewing", default_value_t = 0)]
pub padding: u16,
#[arg(long = "color", help_heading = "Markdown Viewing",
hide = true,
default_value_t = ColorMode::Auto,
default_value_if("color_always", "true", "always"),
default_value_if("color_never", "true", "never"))]
pub color: ColorMode,
#[arg(short = 'c', help_heading = "Markdown Viewing")]
color_always: bool,
#[arg(short = 'C', help_heading = "Markdown Viewing")]
color_never: bool,
#[arg(
long,
value_name = "command",
help_heading = "Markdown Viewing",
env = "MCAT_PAGER",
default_value = "less -r"
)]
pub pager: String,
#[arg(long = "paging", help_heading = "Markdown Viewing",
hide = true,
default_value_t = PagingMode::Auto,
default_value_if("paging_always", "true", "always"),
default_value_if("paging_never", "true", "never"))]
pub paging: PagingMode,
#[arg(short = 'p', help_heading = "Markdown Viewing")]
paging_always: bool,
#[arg(short = 'P', help_heading = "Markdown Viewing")]
paging_never: bool,
#[arg(long, help_heading = "Image/Video Viewing")]
kitty: bool,
#[arg(long, help_heading = "Image/Video Viewing")]
iterm: bool,
#[arg(long, help_heading = "Image/Video Viewing")]
sixel: bool,
#[arg(long, help_heading = "Image/Video Viewing")]
ascii: bool,
#[arg(long, help_heading = "Image/Video Viewing")]
pub no_center: bool,
#[arg(
long,
value_name = "size",
help_heading = "Image/Video Viewing",
default_value = "80%"
)]
pub img_width: String,
#[arg(
long,
value_name = "size",
help_heading = "Image/Video Viewing",
default_value = "40%"
)]
pub img_height: String,
#[arg(long, value_name = "level", help_heading = "Image/Video Viewing")]
pub img_zoom: Option<usize>,
#[arg(long, value_name = "pixels", help_heading = "Image/Video Viewing")]
pub img_x_offset: Option<i32>,
#[arg(long, value_name = "pixels", help_heading = "Image/Video Viewing")]
pub img_y_offset: Option<i32>,
#[arg(long, help_heading = "Conversion")]
pub style_html: bool,
#[arg(long, short = 'a', help_heading = "Directory Listing")]
pub hidden: bool,
#[arg(long, help_heading = "Directory Listing")]
pub hyprlink: bool,
#[arg(long, help_heading = "Directory Listing",
default_value_t = SortMode::Name,
default_value_if("sort_type", "true", "type"),
default_value_if("sort_size", "true", "size"))]
pub sort: SortMode,
#[arg(short = 'X', help_heading = "Directory Listing")]
sort_type: bool,
#[arg(short = 'S', help_heading = "Directory Listing")]
sort_size: bool,
#[arg(long, short = 'r', help_heading = "Directory Listing")]
pub reverse: bool,
#[arg(
long,
value_name = "size",
help_heading = "Directory Listing",
default_value = "3c"
)]
pub ls_x_padding: String,
#[arg(
long,
value_name = "size",
help_heading = "Directory Listing",
default_value = "2c"
)]
pub ls_y_padding: String,
#[arg(
long,
value_name = "size",
help_heading = "Directory Listing",
default_value = "2c"
)]
pub ls_min_width: String,
#[arg(
long,
value_name = "size",
help_heading = "Directory Listing",
default_value = "16c"
)]
pub ls_max_width: String,
#[arg(
long,
value_name = "size",
help_heading = "Directory Listing",
default_value = "2c"
)]
pub ls_height: String,
#[arg(
long,
value_name = "count",
help_heading = "Directory Listing",
default_value_t = 20
)]
pub ls_items_per_row: usize,
#[arg(long, value_name = "shell", help_heading = "System Operations")]
pub generate: Option<Shell>,
#[arg(long, help_heading = "System Operations")]
pub fetch_chromium: bool,
#[arg(long, help_heading = "System Operations")]
pub fetch_ffmpeg: bool,
#[arg(long, help_heading = "System Operations")]
pub fetch_clean: bool,
#[arg(short = 'v', long, help_heading = "Core Options")]
pub verbose: bool,
#[arg(skip)]
pub wininfo: Option<Wininfo>,
#[arg(skip)]
pub env_id: Option<EnvIdentifiers>,
#[arg(skip)]
pub encoder: Option<RasterEncoder>,
}
impl McatConfig {
pub fn finalize(&mut self) -> anyhow::Result<()> {
let env = term_misc::EnvIdentifiers::new();
let spx = Some(self.spx.as_ref());
let sc = Some(self.sc.as_ref());
let wininfo = Wininfo::new(spx, sc, Some(self.scalex), Some(self.scaley), &env)?;
let encoder = if self.kitty {
RasterEncoder::Kitty
} else if self.iterm {
RasterEncoder::Iterm
} else if self.sixel {
RasterEncoder::Sixel
} else if self.ascii {
RasterEncoder::Ascii
} else {
RasterEncoder::auto_detect(&env)
};
debug!(
?encoder,
?self.output,
?self.theme,
?self.md_image,
?self.color,
?self.paging,
sc_width = wininfo.sc_width,
sc_height = wininfo.sc_height,
spx_width = wininfo.spx_width,
spx_height = wininfo.spx_height,
is_tmux = wininfo.is_tmux,
needs_inline = wininfo.needs_inline,
"config"
);
self.env_id = Some(env);
self.wininfo = Some(wininfo);
self.encoder = Some(encoder);
Ok(())
}
}
fn get_styles() -> Styles {
Styles::styled()
.header(AnsiColor::Green.on_default() | Effects::BOLD)
.literal(AnsiColor::Blue.on_default())
.placeholder(AnsiColor::Yellow.on_default())
.usage(AnsiColor::Magenta.on_default())
}
#[derive(ValueEnum, Clone, Default, Debug)]
pub enum Theme {
Catppuccin,
Nord,
Monokai,
Dracula,
Gruvbox,
OneDark,
Solarized,
TokyoNight,
MakuraiLight,
MakuraiDark,
Ayu,
AyuMirage,
#[default]
Github,
Synthwave,
Material,
RosePine,
Kanagawa,
Vscode,
Everforest,
Autumn,
Spring,
}
impl std::fmt::Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.to_possible_value().unwrap().get_name().fmt(f)
}
}
#[derive(ValueEnum, Clone, PartialEq, Debug)]
pub enum OutputFormat {
Html,
Md,
Image,
Inline,
Interactive,
}
#[derive(ValueEnum, Clone, PartialEq, Default, Debug)]
pub enum ColorMode {
Never,
Always,
#[default]
Auto,
}
impl std::fmt::Display for ColorMode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.to_possible_value().unwrap().get_name().fmt(f)
}
}
#[derive(ValueEnum, Clone, PartialEq, Default, Debug)]
pub enum PagingMode {
Never,
Always,
#[default]
Auto,
}
impl std::fmt::Display for PagingMode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.to_possible_value().unwrap().get_name().fmt(f)
}
}
#[derive(ValueEnum, Clone, Default, PartialEq, Debug)]
pub enum MdImageMode {
All,
Small,
None,
#[default]
Auto,
}
impl std::fmt::Display for MdImageMode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.to_possible_value().unwrap().get_name().fmt(f)
}
}
#[derive(ValueEnum, Clone, Default)]
pub enum ImageProtocol {
#[default]
Auto,
Kitty,
Iterm,
Sixel,
Ascii,
}
impl std::fmt::Display for ImageProtocol {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.to_possible_value().unwrap().get_name().fmt(f)
}
}
#[derive(ValueEnum, Clone, Default, Debug)]
pub enum SortMode {
#[default]
Name,
Size,
Time,
Type,
}
impl std::fmt::Display for SortMode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.to_possible_value().unwrap().get_name().fmt(f)
}
}