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