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
 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}