termio/
termio.rs

1use crate::border::BorderStyle;
2use crate::color::Color;
3use crate::decoration::Decoration;
4use crate::style::Style;
5use std::collections::HashMap;
6use std::error::Error;
7use std::fmt;
8use std::str::FromStr;
9
10#[derive(Debug)]
11pub struct Termio {
12    styles: HashMap<String, Style>,
13}
14
15/// Custom error type for TCSS parsing errors
16#[derive(Debug)]
17pub enum ParseError {
18    InvalidSyntax(String),
19    DuplicateElement(String),
20}
21
22impl fmt::Display for ParseError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            ParseError::InvalidSyntax(msg) => write!(f, "Invalid syntax: {}", msg),
26            ParseError::DuplicateElement(name) => write!(f, "Duplicate element name: {}", name),
27        }
28    }
29}
30
31impl Error for ParseError {}
32
33impl From<String> for ParseError {
34    fn from(s: String) -> Self {
35        ParseError::InvalidSyntax(s)
36    }
37}
38
39impl Termio {
40    /// Creates a new Termio with an empty style map.
41    pub fn new() -> Self {
42        Termio {
43            styles: HashMap::new(),
44        }
45    }
46
47    pub fn from_file(path: &str) -> Result<Self, ParseError> {
48        let mut tcss = Self::new();
49        let content = std::fs::read_to_string(path).map_err(|e| ParseError::InvalidSyntax(e.to_string()))?;
50        tcss.parse(&content)?;
51        Ok(tcss)
52    }
53
54    /// Retrieves a style by name, returning None if not found.
55    pub fn get_style(&self, name: &str) -> Option<Style> {
56        self.styles.get(name).cloned()
57    }
58
59    /// Parses TCSS content and populates the style map.
60    pub fn parse(&mut self, content: &str) -> Result<(), ParseError> {
61        let mut lines = content.lines().peekable();
62        let mut current_style = None;
63        let mut current_name = None;
64
65        while let Some(line) = lines.next() {
66            let line = line.trim();
67            // Skip empty lines and comments
68            if line.is_empty() || line.starts_with("//") {
69                continue;
70            }
71
72            if line.starts_with("@element") {
73                if let Some(name) = current_name {
74                    if self.styles.contains_key(&name) {
75                        return Err(ParseError::DuplicateElement(name));
76                    }
77                    self.styles.insert(name, current_style.unwrap_or_default());
78                }
79
80                let name = line
81                    .split('"')
82                    .nth(1)
83                    .ok_or_else(|| ParseError::InvalidSyntax("Missing element name".to_string()))?;
84                current_name = Some(name.to_string());
85                current_style = Some(Style::new());
86            } else if let Some(style) = &mut current_style {
87                if line == "}" {
88                    if let Some(name) = current_name.take() {
89                        if self.styles.contains_key(&name) {
90                            return Err(ParseError::DuplicateElement(name));
91                        }
92                        self.styles
93                            .insert(name, current_style.take().unwrap_or_default());
94                    }
95                } else {
96                    let parts: Vec<&str> = line.split(':').collect();
97                    if parts.len() != 2 {
98                        return Err(ParseError::InvalidSyntax(format!(
99                            "Invalid property: {}",
100                            line
101                        )));
102                    }
103
104                    let property = parts[0].trim();
105                    let value = parts[1].trim().trim_end_matches(';');
106
107                    match property {
108                        "color" => {
109                            style.fg = Some(
110                                Color::from_str(value)
111                                    .map_err(|e| ParseError::InvalidSyntax(e.to_string()))?,
112                            )
113                        }
114                        "background" => {
115                            style.bg = Some(
116                                Color::from_str(value)
117                                    .map_err(|e| ParseError::InvalidSyntax(e.to_string()))?,
118                            )
119                        }
120                        "decoration" => style.decoration = Some(self.parse_decoration(value)?),
121                        "padding" => {
122                            let values: Vec<&str> = value.split_whitespace().collect();
123                            match values.len() {
124                                1 => {
125                                    let pad = values[0].parse().map_err(|_| {
126                                        ParseError::InvalidSyntax(format!(
127                                            "Invalid padding value: {}",
128                                            value
129                                        ))
130                                    })?;
131                                    style.padding = Some(pad);
132                                    style.padding_top = Some(pad);
133                                    style.padding_bottom = Some(pad);
134                                    style.padding_left = Some(pad);
135                                    style.padding_right = Some(pad);
136                                }
137                                2 => {
138                                    let v = values[0].parse().map_err(|_| {
139                                        ParseError::InvalidSyntax(format!(
140                                            "Invalid padding value: {}",
141                                            value
142                                        ))
143                                    })?;
144                                    let h = values[1].parse().map_err(|_| {
145                                        ParseError::InvalidSyntax(format!(
146                                            "Invalid padding value: {}",
147                                            value
148                                        ))
149                                    })?;
150                                    style.padding_top = Some(v);
151                                    style.padding_bottom = Some(v);
152                                    style.padding_left = Some(h);
153                                    style.padding_right = Some(h);
154                                }
155                                4 => {
156                                    let top = values[0].parse().map_err(|_| {
157                                        ParseError::InvalidSyntax(format!(
158                                            "Invalid padding value: {}",
159                                            value
160                                        ))
161                                    })?;
162                                    let right = values[1].parse().map_err(|_| {
163                                        ParseError::InvalidSyntax(format!(
164                                            "Invalid padding value: {}",
165                                            value
166                                        ))
167                                    })?;
168                                    let bottom = values[2].parse().map_err(|_| {
169                                        ParseError::InvalidSyntax(format!(
170                                            "Invalid padding value: {}",
171                                            value
172                                        ))
173                                    })?;
174                                    let left = values[3].parse().map_err(|_| {
175                                        ParseError::InvalidSyntax(format!(
176                                            "Invalid padding value: {}",
177                                            value
178                                        ))
179                                    })?;
180                                    style.padding_top = Some(top);
181                                    style.padding_right = Some(right);
182                                    style.padding_bottom = Some(bottom);
183                                    style.padding_left = Some(left);
184                                }
185                                _ => {
186                                    return Err(ParseError::InvalidSyntax(
187                                        "Invalid padding format. Use 1, 2, or 4 values".to_string(),
188                                    ))
189                                }
190                            }
191                        }
192                        "padding-top" => {
193                            style.padding_top = Some(value.parse().map_err(|_| {
194                                ParseError::InvalidSyntax(format!(
195                                    "Invalid padding-top value: {}",
196                                    value
197                                ))
198                            })?)
199                        }
200                        "padding-bottom" => {
201                            style.padding_bottom = Some(value.parse().map_err(|_| {
202                                ParseError::InvalidSyntax(format!(
203                                    "Invalid padding-bottom value: {}",
204                                    value
205                                ))
206                            })?)
207                        }
208                        "padding-left" => {
209                            style.padding_left = Some(value.parse().map_err(|_| {
210                                ParseError::InvalidSyntax(format!(
211                                    "Invalid padding-left value: {}",
212                                    value
213                                ))
214                            })?)
215                        }
216                        "padding-right" => {
217                            style.padding_right = Some(value.parse().map_err(|_| {
218                                ParseError::InvalidSyntax(format!(
219                                    "Invalid padding-right value: {}",
220                                    value
221                                ))
222                            })?)
223                        }
224                        "margin" => {
225                            let values: Vec<&str> = value.split_whitespace().collect();
226                            match values.len() {
227                                1 => {
228                                    let margin = values[0].parse().map_err(|_| {
229                                        ParseError::InvalidSyntax(format!(
230                                            "Invalid margin value: {}",
231                                            value
232                                        ))
233                                    })?;
234                                    style.margin = Some(margin);
235                                    style.margin_top = Some(margin);
236                                    style.margin_bottom = Some(margin);
237                                    style.margin_left = Some(margin);
238                                    style.margin_right = Some(margin);
239                                }
240                                2 => {
241                                    let v = values[0].parse().map_err(|_| {
242                                        ParseError::InvalidSyntax(format!(
243                                            "Invalid margin value: {}",
244                                            value
245                                        ))
246                                    })?;
247                                    let h = values[1].parse().map_err(|_| {
248                                        ParseError::InvalidSyntax(format!(
249                                            "Invalid margin value: {}",
250                                            value
251                                        ))
252                                    })?;
253                                    style.margin_top = Some(v);
254                                    style.margin_bottom = Some(v);
255                                    style.margin_left = Some(h);
256                                    style.margin_right = Some(h);
257                                }
258                                4 => {
259                                    let top = values[0].parse().map_err(|_| {
260                                        ParseError::InvalidSyntax(format!(
261                                            "Invalid margin value: {}",
262                                            value
263                                        ))
264                                    })?;
265                                    let right = values[1].parse().map_err(|_| {
266                                        ParseError::InvalidSyntax(format!(
267                                            "Invalid margin value: {}",
268                                            value
269                                        ))
270                                    })?;
271                                    let bottom = values[2].parse().map_err(|_| {
272                                        ParseError::InvalidSyntax(format!(
273                                            "Invalid margin value: {}",
274                                            value
275                                        ))
276                                    })?;
277                                    let left = values[3].parse().map_err(|_| {
278                                        ParseError::InvalidSyntax(format!(
279                                            "Invalid margin value: {}",
280                                            value
281                                        ))
282                                    })?;
283                                    style.margin_top = Some(top);
284                                    style.margin_right = Some(right);
285                                    style.margin_bottom = Some(bottom);
286                                    style.margin_left = Some(left);
287                                }
288                                _ => {
289                                    return Err(ParseError::InvalidSyntax(
290                                        "Invalid margin format. Use 1, 2, or 4 values".to_string(),
291                                    ))
292                                }
293                            }
294                        }
295                        "margin-top" => {
296                            style.margin_top = Some(value.parse().map_err(|_| {
297                                ParseError::InvalidSyntax(format!(
298                                    "Invalid margin-top value: {}",
299                                    value
300                                ))
301                            })?)
302                        }
303                        "margin-bottom" => {
304                            style.margin_bottom = Some(value.parse().map_err(|_| {
305                                ParseError::InvalidSyntax(format!(
306                                    "Invalid margin-bottom value: {}",
307                                    value
308                                ))
309                            })?)
310                        }
311                        "margin-left" => {
312                            style.margin_left = Some(value.parse().map_err(|_| {
313                                ParseError::InvalidSyntax(format!(
314                                    "Invalid margin-left value: {}",
315                                    value
316                                ))
317                            })?)
318                        }
319                        "margin-right" => {
320                            style.margin_right = Some(value.parse().map_err(|_| {
321                                ParseError::InvalidSyntax(format!(
322                                    "Invalid margin-right value: {}",
323                                    value
324                                ))
325                            })?)
326                        }
327                        "border-color" => {
328                            style.border_color = Some(
329                                Color::from_str(value)
330                                    .map_err(|e| ParseError::InvalidSyntax(e.to_string()))?,
331                            )
332                        }
333                        "border-style" => {
334                            style.border_style = Some(self.parse_border_style(value)?)
335                        }
336                        "border" => {
337                            let (s, c) = value.split_once(" ").unwrap();
338                            style.border_style = Some(self.parse_border_style(s)?);
339                            style.border_color = Some(
340                                Color::from_str(c)
341                                    .map_err(|e| ParseError::InvalidSyntax(e.to_string()))?,
342                            );
343                        }
344                        _ => {
345                            return Err(ParseError::InvalidSyntax(format!(
346                                "Unknown property: {}",
347                                property
348                            )))
349                        }
350                    }
351                }
352            }
353        }
354
355        // Handle the last style if exists
356        if let Some(name) = current_name {
357            if self.styles.contains_key(&name) {
358                return Err(ParseError::DuplicateElement(name));
359            }
360            self.styles.insert(name, current_style.unwrap_or_default());
361        }
362
363        Ok(())
364    }
365
366    /// Parses a decoration string into a vector of decorations
367    fn parse_decoration(&self, value: &str) -> Result<Vec<Decoration>, ParseError> {
368        value
369            .split_whitespace()
370            .map(|d| Decoration::from_str(d).map_err(|e| ParseError::InvalidSyntax(e.to_string())))
371            .collect()
372    }
373
374    /// Parses a border style string into a BorderStyle
375    fn parse_border_style(&self, value: &str) -> Result<BorderStyle, ParseError> {
376        BorderStyle::from_str(value).map_err(|e| ParseError::InvalidSyntax(e.to_string()))
377    }
378
379    pub fn add_style(&mut self, name: &str, style: Style) {
380        self.styles.insert(name.to_string(), style);
381    }
382}