1mod theme_printer;
2mod util;
3mod vscode;
4
5use std::fs::{self, File};
6use std::io::Write;
7use std::path::PathBuf;
8use std::str::FromStr;
9
10use anyhow::{anyhow, Context, Result};
11use convert_case::{Case, Casing};
12use gpui::serde_json;
13use log::LevelFilter;
14use serde::Deserialize;
15use simplelog::SimpleLogger;
16use theme::{default_color_scales, Appearance, ThemeFamily};
17use vscode::VsCodeThemeConverter;
18
19use crate::theme_printer::ThemeFamilyPrinter;
20use crate::vscode::VsCodeTheme;
21
22#[derive(Debug, Deserialize)]
23struct FamilyMetadata {
24 pub name: String,
25 pub author: String,
26 pub themes: Vec<ThemeMetadata>,
27}
28
29#[derive(Debug, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum ThemeAppearanceJson {
32 Light,
33 Dark,
34}
35
36impl From<ThemeAppearanceJson> for Appearance {
37 fn from(value: ThemeAppearanceJson) -> Self {
38 match value {
39 ThemeAppearanceJson::Light => Self::Light,
40 ThemeAppearanceJson::Dark => Self::Dark,
41 }
42 }
43}
44
45#[derive(Debug, Deserialize)]
46pub struct ThemeMetadata {
47 pub name: String,
48 pub file_name: String,
49 pub appearance: ThemeAppearanceJson,
50}
51
52fn main() -> Result<()> {
53 SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
54
55 println!("Creating path: assets/themes/src/vscode/");
56 let vscode_themes_path = PathBuf::from_str("assets/themes/src/vscode/")?;
57
58 let mut theme_families = Vec::new();
59
60 for theme_family_dir in fs::read_dir(&vscode_themes_path)? {
61 println!("Reading directory: {:?}", vscode_themes_path);
62
63 let theme_family_dir = theme_family_dir?;
64
65 if theme_family_dir.file_name().to_str() == Some(".DS_Store") {
66 continue;
67 }
68
69 let theme_family_slug = theme_family_dir
70 .path()
71 .file_stem()
72 .ok_or(anyhow!("no file stem"))
73 .map(|stem| stem.to_string_lossy().to_string())?;
74
75 println!(
76 "Opening file: {:?}",
77 theme_family_dir.path().join("family.json")
78 );
79 let family_metadata_file = File::open(theme_family_dir.path().join("family.json"))
80 .context(format!("no `family.json` found for '{theme_family_slug}'"))?;
81
82 let family_metadata: FamilyMetadata = serde_json::from_reader(family_metadata_file)
83 .context(format!(
84 "failed to parse `family.json` for '{theme_family_slug}'"
85 ))?;
86
87 let mut themes = Vec::new();
88
89 for theme_metadata in family_metadata.themes {
90 let theme_file_path = theme_family_dir.path().join(&theme_metadata.file_name);
91
92 let theme_file = match File::open(&theme_file_path) {
93 Ok(file) => file,
94 Err(_) => {
95 println!("Failed to open file at path: {:?}", theme_file_path);
96 continue;
97 }
98 };
99
100 let vscode_theme: VsCodeTheme = serde_json::from_reader(theme_file)
101 .context(format!("failed to parse theme {theme_file_path:?}"))?;
102
103 let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata);
104
105 let theme = converter.convert()?;
106
107 themes.push(theme);
108 }
109
110 let theme_family = ThemeFamily {
111 id: uuid::Uuid::new_v4().to_string(),
112 name: family_metadata.name.into(),
113 author: family_metadata.author.into(),
114 themes,
115 scales: default_color_scales(),
116 };
117
118 theme_families.push(theme_family);
119 }
120
121 let themes_output_path = PathBuf::from_str("crates/theme2/src/themes")?;
122
123 let mut theme_modules = Vec::new();
124
125 for theme_family in theme_families {
126 let theme_family_slug = theme_family.name.to_string().to_case(Case::Snake);
127
128 let mut output_file =
129 File::create(themes_output_path.join(format!("{theme_family_slug}.rs")))?;
130 println!(
131 "Creating file: {:?}",
132 themes_output_path.join(format!("{theme_family_slug}.rs"))
133 );
134
135 let theme_module = format!(
136 r#"
137 use gpui::rgba;
138
139 use crate::{{
140 default_color_scales, Appearance, GitStatusColors, PlayerColor, PlayerColors, StatusColors,
141 SyntaxTheme, SystemColors, ThemeColors, ThemeFamily, ThemeStyles, ThemeVariant,
142 }};
143
144 pub fn {theme_family_slug}() -> ThemeFamily {{
145 {theme_family_definition}
146 }}
147 "#,
148 theme_family_definition = format!("{:#?}", ThemeFamilyPrinter::new(theme_family))
149 );
150
151 output_file.write_all(theme_module.as_bytes())?;
152
153 theme_modules.push(theme_family_slug);
154 }
155
156 println!(
157 "Creating file: {:?}",
158 themes_output_path.join(format!("mod.rs"))
159 );
160 let mut mod_rs_file = File::create(themes_output_path.join(format!("mod.rs")))?;
161
162 let mod_rs_contents = format!(
163 r#"
164 {mod_statements}
165
166 {use_statements}
167 "#,
168 mod_statements = theme_modules
169 .iter()
170 .map(|module| format!("mod {module};"))
171 .collect::<Vec<_>>()
172 .join("\n"),
173 use_statements = theme_modules
174 .iter()
175 .map(|module| format!("pub use {module}::*;"))
176 .collect::<Vec<_>>()
177 .join("\n")
178 );
179
180 mod_rs_file.write_all(mod_rs_contents.as_bytes())?;
181
182 Ok(())
183}