use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr};
use clap::Parser;
use serde_json::Value;
use crate::delta::CLIDEMethod;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[arg(
short,
long,
value_name = "VARIATIONS",
default_value = "all",
verbatim_doc_comment
)]
pub styles: ColorPaletteStyles,
#[arg(short, long, value_name = "PATH", value_delimiter = ',')]
pub output: Option<Vec<PathBuf>>,
#[arg(short, long, value_name = "PATH")]
pub dir_output: Option<PathBuf>,
#[arg(short, long, value_enum, default_value = "de2000")]
pub method: CLIDEMethod,
#[arg(short, long, action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(value_name = "PALETTE", verbatim_doc_comment)]
pub color_palette: ColorPalette,
#[arg(value_name = "FILE", value_delimiter = ',')]
pub process: Vec<PathBuf>,
}
#[derive(Clone, Debug)]
pub enum ColorPaletteStyles {
All,
Some { styles: Vec<String> },
None,
}
impl FromStr for ColorPaletteStyles {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let style = match s {
"all" | "ALL" => Self::All,
"none" | "NONE" | "no" | "NO" => Self::None,
some => Self::Some {
styles: {
let mut vars = Vec::new();
for var in some.split(',') {
if var.is_empty() {
return Err("One of the variations seems to be an empty string. Do you have a double comma in your variations list (-v)?".to_string());
};
vars.push(var.to_string())
}
if vars.is_empty() {
return Err("No styles selected".to_string());
};
vars
},
},
};
Ok(style)
}
}
#[derive(Clone, Debug)]
pub enum ColorPalette {
RawJSON { map: serde_json::Map<String, Value> },
Catppuccin,
Dracula,
Edge,
Everforest,
Gruvbox,
GruvboxMaterial,
Nord,
OneDark,
RosePine,
Solarized,
TokyoNight,
}
impl std::fmt::Display for ColorPalette {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ColorPalette::RawJSON { map } => {
write!(f, "JSON: {}", serde_json::to_string(map).unwrap())
}
ColorPalette::Catppuccin => write!(f, "catppuccin"),
ColorPalette::Dracula => write!(f, "dracula"),
ColorPalette::Edge => write!(f, "edge"),
ColorPalette::Everforest => write!(f, "everforest"),
ColorPalette::Gruvbox => write!(f, "gruvbox"),
ColorPalette::GruvboxMaterial => write!(f, "gruvbox-material"),
ColorPalette::Nord => write!(f, "nord"),
ColorPalette::OneDark => write!(f, "onedark"),
ColorPalette::RosePine => write!(f, "rose-pine"),
ColorPalette::Solarized => write!(f, "solarized"),
ColorPalette::TokyoNight => write!(f, "tokyo-night"),
}
}
}
impl FromStr for ColorPalette {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("JSON: ") {
let jsonstr = &s[5..];
let json: Value = serde_json::from_str(jsonstr).map_err(|err| err.to_string())?;
let Value::Object(map) = json else {
return Err("Encountered error while parsing inline JSON string: the string appears to not be a JSON object".to_string());
};
return Ok(ColorPalette::RawJSON { map });
};
let palette = match s {
"catppuccin" => ColorPalette::Catppuccin,
"dracula" => ColorPalette::Dracula,
"edge" => ColorPalette::Edge,
"everforest" => ColorPalette::Everforest,
"gruvbox" => ColorPalette::Gruvbox,
"gruvbox-material" | "gruvbox_material" | "gruvboxmaterial" => {
ColorPalette::GruvboxMaterial
}
"nord" => ColorPalette::Nord,
"onedark" | "one_dark" | "one-dark" => ColorPalette::OneDark,
"rose-pine" | "rose_pine" | "rosepine" => ColorPalette::RosePine,
"solarized" => ColorPalette::Solarized,
"tokyo-night" | "tokyo_night" | "tokyonight" => ColorPalette::TokyoNight,
external => {
let external: PathBuf = external.into();
if !external.is_file() {
return Err(format!("Theme source file `{s}` appears to not be a file."));
};
let file = File::open(external).map_err(|err| err.to_string())?;
let file = BufReader::new(file);
let json = serde_json::from_reader(file)
.map_err(|err| format!("Error while parsing JSON content of {s}: {err}"))?;
let Value::Object(map) = json else {
return Err("Encountered error while parsing JSON theme file: the contents of the file are valid JSON but do not appear to be a JSON object".to_string());
};
ColorPalette::RawJSON { map }
}
};
Ok(palette)
}
}