main.rs

  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}