1use std::{
5 fs,
6 path::Path,
7 str::FromStr,
8 num::ParseIntError,
9
10 io::{
11 self,
12 ErrorKind
13 }
14};
15
16use chromakitx::{
17 AnsiColor,
18 CssColor,
19 XtermColor,
20 AnyColor
21};
22
23use super::{
24 error::Error,
25
26 config::{
27 LogoConfig,
28 ColorConfig,
29 ColorDef
30 }
31};
32
33pub struct LogoParser;
34
35impl LogoParser {
36 pub fn from(path: &Path) -> Result<(Vec<String>, Vec<String>, ColorConfig), Error> {
37 let content: String = fs::read_to_string(path).map_err(|err: io::Error| {
38 if err.kind() == ErrorKind::NotFound {
39 return Error::FileNotFound(path.to_path_buf());
40 }
41
42 Error::IoError(err)
43 })?;
44
45 let config: LogoConfig = json5::from_str(&content)?;
46 config.validate().map_err(Error::ValidationError)?;
47
48 let large: Vec<String> = config.get_large_logo().unwrap_or_default();
49 let small: Vec<String> = config.get_small_logo().unwrap_or_default();
50
51 if large.is_empty() && small.is_empty() {
52 return Err(Error::ValidationError("No valid logo found".to_string()));
53 }
54
55 let color_config: ColorConfig = Self::parse_colors(&config)?;
56 Ok((large, small, color_config))
57 }
58
59 fn parse_colors(config: &LogoConfig) -> Result<ColorConfig, Error> {
60 let color_defs: &[ColorDef] = config.get_color_defs().ok_or_else(|| Error::ValidationError("Color definitions not found".to_string()))?;
61
62 if color_defs.is_empty() {
63 return Err(Error::ValidationError("Color definitions array is empty".to_string()));
64 }
65
66 let title_ref: &str = config.get_title_color_ref().ok_or_else(|| Error::ValidationError("Title color reference not found".to_string()))?;
67 let title_color: AnyColor = Self::resolve_color_reference(title_ref, color_defs)?;
68
69 let keys_ref: &str = config.get_keys_color_ref().ok_or_else(|| Error::ValidationError("Keys color reference not found".to_string()))?;
70 let keys_color: AnyColor = Self::resolve_color_reference(keys_ref, color_defs)?;
71
72 let mut ascii_colors: Vec<AnyColor> = Vec::with_capacity(color_defs.len());
73
74 for color_def in color_defs {
75 (&mut ascii_colors).push(Self::parse_color_def(color_def)?);
76 }
77
78 Ok(ColorConfig {
79 title_color,
80 keys_color,
81 ascii_colors
82 })
83 }
84
85 fn resolve_color_reference(reference: &str, color_defs: &[ColorDef]) -> Result<AnyColor, Error> {
86 let reference: &str = reference.trim();
87
88 if let Some(idx_str) = reference.strip_prefix('%') {
89 let idx: usize = idx_str.parse().map_err(|_: ParseIntError| Error::ColorParseError(format!("Invalid color index: {}", idx_str)))?;
90
91 if idx >= color_defs.len() {
92 return Err(Error::ColorParseError(format!("Color index out of bounds: {}", idx), ));
93 }
94
95 return Self::parse_color_def(&color_defs[idx]);
96 }
97
98 Self::parse_color_name(reference)
99 }
100
101 fn parse_color_def(color_def: &ColorDef) -> Result<AnyColor, Error> {
102 match color_def {
103 ColorDef::Simple(name) => Self::parse_color_name(name),
104
105 ColorDef::Named(map) => {
106 if map.len() != 1 {
107 return Err(Error::ColorParseError("Color definition object must have exactly one entry".to_string()));
108 }
109
110 let (color_name, color_type): (&String, &String) = map.iter().next().ok_or_else(|| Error::ColorParseError("Empty color definition".to_string()))?;
111
112 if color_type.eq_ignore_ascii_case("ansi") {
113 Self::parse_ansi_color(color_name)
114 } else if color_type.eq_ignore_ascii_case("xterm") {
115 Self::parse_xterm_color(color_name)
116 } else if color_type.eq_ignore_ascii_case("css") {
117 Self::parse_css_color(color_name)
118 } else {
119 Err(Error::ColorParseError(format!("Unknown color type: {}", color_type)))
120 }
121 }
122 }
123 }
124
125 fn parse_color_name(name: &str) -> Result<AnyColor, Error> {
126 AnsiColor::from_str(name).map(AnyColor::Ansi).or_else(|_: String| {
127 CssColor::from_str(name).map(AnyColor::Css)
128 }).or_else(|_: String| {
129 XtermColor::from_str(name).map(AnyColor::Xterm)
130 }).map_err(|_: String| Error::ColorParseError(format!("Unknown color name: {}", name)))
131 }
132
133 fn parse_ansi_color(name: &str) -> Result<AnyColor, Error> {
134 AnsiColor::from_str(name).map(AnyColor::Ansi).map_err(|_: String| Error::ColorParseError(format!("Unknown ANSI color: {}", name)))
135 }
136
137 fn parse_xterm_color(name: &str) -> Result<AnyColor, Error> {
138 XtermColor::from_str(name).map(AnyColor::Xterm).map_err(|_: String| Error::ColorParseError(format!("Unknown Xterm color: {}", name)))
139 }
140
141 fn parse_css_color(name: &str) -> Result<AnyColor, Error> {
142 CssColor::from_str(name).map(AnyColor::Css).map_err(|_: String| Error::ColorParseError(format!("Unknown CSS color: {}", name)))
143 }
144}