marco_core/render/
theme_meta.rs1#[derive(Debug, Clone, Default, PartialEq, Eq)]
24pub struct ThemeMetadata {
25 pub name: Option<String>,
26 pub author: Option<String>,
27 pub license: Option<String>,
28 pub version: Option<String>,
29 pub description: Option<String>,
30}
31
32pub fn parse_theme_metadata(css: &str) -> ThemeMetadata {
38 ThemeMetadata {
39 name: extract_token(css, "--theme-name"),
40 author: extract_token(css, "--theme-author"),
41 license: extract_token(css, "--theme-license"),
42 version: extract_token(css, "--theme-version"),
43 description: extract_token(css, "--theme-description"),
44 }
45}
46
47fn extract_token(css: &str, property: &str) -> Option<String> {
48 let mut search_from = 0;
49 while let Some(offset) = css[search_from..].find(property) {
50 let start = search_from + offset;
51 let after_name = &css[start + property.len()..];
52 let next_non_space = after_name.trim_start();
56 if let Some(value) = next_non_space.strip_prefix(':') {
57 let semicolon = value.find(';')?;
58 let raw = value[..semicolon].trim();
59 let quote = raw.chars().next()?;
60 if quote == '\'' || quote == '"' {
61 if let Some(rest) = raw.strip_prefix(quote) {
62 if let Some(unquoted) = rest.strip_suffix(quote) {
63 return Some(unquoted.trim().to_string());
64 }
65 }
66 }
67 return None;
68 }
69 search_from = start + property.len();
70 }
71 None
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn parses_all_fields_single_quotes() {
80 let css = r#"
81:root {
82 --theme-name: 'GitHub';
83 --theme-author: 'Kim Skov Rasmussen';
84 --theme-license: 'MIT';
85 --theme-version: '0.24.0';
86 --theme-description: 'GitHub-inspired theme';
87 --text-color: #1f2328;
88}
89"#;
90 let meta = parse_theme_metadata(css);
91 assert_eq!(meta.name.as_deref(), Some("GitHub"));
92 assert_eq!(meta.author.as_deref(), Some("Kim Skov Rasmussen"));
93 assert_eq!(meta.license.as_deref(), Some("MIT"));
94 assert_eq!(meta.version.as_deref(), Some("0.24.0"));
95 assert_eq!(meta.description.as_deref(), Some("GitHub-inspired theme"));
96 }
97
98 #[test]
99 fn parses_double_quotes() {
100 let css = r#"--theme-name: "Nord";"#;
101 assert_eq!(parse_theme_metadata(css).name.as_deref(), Some("Nord"));
102 }
103
104 #[test]
105 fn missing_fields_are_none() {
106 let css = ":root { --text-color: #333; }";
107 let meta = parse_theme_metadata(css);
108 assert_eq!(meta, ThemeMetadata::default());
109 }
110
111 #[test]
112 fn does_not_match_longer_property_names_with_same_prefix() {
113 let css = "--theme-name-extra: 'not-a-name'; --theme-name: 'Real';";
114 let meta = parse_theme_metadata(css);
115 assert_eq!(meta.name.as_deref(), Some("Real"));
116 }
117}