main.rs

  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}