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