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}