use std::{
fs,
path::Path,
str::FromStr,
num::ParseIntError,
io::{
self,
ErrorKind
}
};
use chromakitx::{
AnsiColor,
CssColor,
XtermColor,
AnyColor
};
use super::{
error::Error,
config::{
LogoConfig,
ColorConfig,
ColorDef
}
};
pub struct LogoParser;
impl LogoParser {
pub fn from(path: &Path) -> Result<(Vec<String>, Vec<String>, ColorConfig), Error> {
let content: String = fs::read_to_string(path).map_err(|err: io::Error| {
if err.kind() == ErrorKind::NotFound {
return Error::FileNotFound(path.to_path_buf());
}
Error::IoError(err)
})?;
let config: LogoConfig = json5::from_str(&content)?;
config.validate().map_err(Error::ValidationError)?;
let large: Vec<String> = config.get_large_logo().unwrap_or_default();
let small: Vec<String> = config.get_small_logo().unwrap_or_default();
if large.is_empty() && small.is_empty() {
return Err(Error::ValidationError("No valid logo found".to_string()));
}
let color_config: ColorConfig = Self::parse_colors(&config)?;
Ok((large, small, color_config))
}
fn parse_colors(config: &LogoConfig) -> Result<ColorConfig, Error> {
let color_defs: &[ColorDef] = config.get_color_defs().ok_or_else(|| Error::ValidationError("Color definitions not found".to_string()))?;
if color_defs.is_empty() {
return Err(Error::ValidationError("Color definitions array is empty".to_string()));
}
let title_ref: &str = config.get_title_color_ref().ok_or_else(|| Error::ValidationError("Title color reference not found".to_string()))?;
let title_color: AnyColor = Self::resolve_color_reference(title_ref, color_defs)?;
let keys_ref: &str = config.get_keys_color_ref().ok_or_else(|| Error::ValidationError("Keys color reference not found".to_string()))?;
let keys_color: AnyColor = Self::resolve_color_reference(keys_ref, color_defs)?;
let mut ascii_colors: Vec<AnyColor> = Vec::with_capacity(color_defs.len());
for color_def in color_defs {
(&mut ascii_colors).push(Self::parse_color_def(color_def)?);
}
Ok(ColorConfig {
title_color,
keys_color,
ascii_colors
})
}
fn resolve_color_reference(reference: &str, color_defs: &[ColorDef]) -> Result<AnyColor, Error> {
let reference: &str = reference.trim();
if let Some(idx_str) = reference.strip_prefix('%') {
let idx: usize = idx_str.parse().map_err(|_: ParseIntError| Error::ColorParseError(format!("Invalid color index: {}", idx_str)))?;
if idx >= color_defs.len() {
return Err(Error::ColorParseError(format!("Color index out of bounds: {}", idx), ));
}
return Self::parse_color_def(&color_defs[idx]);
}
Self::parse_color_name(reference)
}
fn parse_color_def(color_def: &ColorDef) -> Result<AnyColor, Error> {
match color_def {
ColorDef::Simple(name) => Self::parse_color_name(name),
ColorDef::Named(map) => {
if map.len() != 1 {
return Err(Error::ColorParseError("Color definition object must have exactly one entry".to_string()));
}
let (color_name, color_type): (&String, &String) = map.iter().next().ok_or_else(|| Error::ColorParseError("Empty color definition".to_string()))?;
if color_type.eq_ignore_ascii_case("ansi") {
Self::parse_ansi_color(color_name)
} else if color_type.eq_ignore_ascii_case("xterm") {
Self::parse_xterm_color(color_name)
} else if color_type.eq_ignore_ascii_case("css") {
Self::parse_css_color(color_name)
} else {
Err(Error::ColorParseError(format!("Unknown color type: {}", color_type)))
}
}
}
}
fn parse_color_name(name: &str) -> Result<AnyColor, Error> {
AnsiColor::from_str(name).map(AnyColor::Ansi).or_else(|_: String| {
CssColor::from_str(name).map(AnyColor::Css)
}).or_else(|_: String| {
XtermColor::from_str(name).map(AnyColor::Xterm)
}).map_err(|_: String| Error::ColorParseError(format!("Unknown color name: {}", name)))
}
fn parse_ansi_color(name: &str) -> Result<AnyColor, Error> {
AnsiColor::from_str(name).map(AnyColor::Ansi).map_err(|_: String| Error::ColorParseError(format!("Unknown ANSI color: {}", name)))
}
fn parse_xterm_color(name: &str) -> Result<AnyColor, Error> {
XtermColor::from_str(name).map(AnyColor::Xterm).map_err(|_: String| Error::ColorParseError(format!("Unknown Xterm color: {}", name)))
}
fn parse_css_color(name: &str) -> Result<AnyColor, Error> {
CssColor::from_str(name).map(AnyColor::Css).map_err(|_: String| Error::ColorParseError(format!("Unknown CSS color: {}", name)))
}
}