Skip to main content

logo_parser/
parser.rs

1// SPDX-FileCopyrightText: 2023 CELESTIFYX Team
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use 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}