mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-11 12:46:07 +00:00
This PR makes the `theme_importer` format the themes that it generates automatically. Release Notes: - N/A
237 lines
6.8 KiB
Rust
237 lines
6.8 KiB
Rust
mod theme_printer;
|
|
mod util;
|
|
mod vscode;
|
|
|
|
use std::fs::{self, File};
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
use std::str::FromStr;
|
|
|
|
use anyhow::{anyhow, Context, Result};
|
|
use convert_case::{Case, Casing};
|
|
use gpui::serde_json;
|
|
use log::LevelFilter;
|
|
use serde::Deserialize;
|
|
use simplelog::SimpleLogger;
|
|
use theme::{Appearance, UserThemeFamily};
|
|
use vscode::VsCodeThemeConverter;
|
|
|
|
use crate::theme_printer::UserThemeFamilyPrinter;
|
|
use crate::vscode::VsCodeTheme;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct FamilyMetadata {
|
|
pub name: String,
|
|
pub author: String,
|
|
pub themes: Vec<ThemeMetadata>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ThemeAppearanceJson {
|
|
Light,
|
|
Dark,
|
|
}
|
|
|
|
impl From<ThemeAppearanceJson> for Appearance {
|
|
fn from(value: ThemeAppearanceJson) -> Self {
|
|
match value {
|
|
ThemeAppearanceJson::Light => Self::Light,
|
|
ThemeAppearanceJson::Dark => Self::Dark,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct ThemeMetadata {
|
|
pub name: String,
|
|
pub file_name: String,
|
|
pub appearance: ThemeAppearanceJson,
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
const SOURCE_PATH: &str = "assets/themes/src/vscode";
|
|
const OUT_PATH: &str = "crates/theme2/src/themes";
|
|
|
|
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
|
|
|
println!("Loading themes source...");
|
|
let vscode_themes_path = PathBuf::from_str(SOURCE_PATH)?;
|
|
if !vscode_themes_path.exists() {
|
|
return Err(anyhow!(format!(
|
|
"Couldn't find {}, make sure it exists",
|
|
SOURCE_PATH
|
|
)));
|
|
}
|
|
|
|
let mut theme_families = Vec::new();
|
|
|
|
for theme_family_dir in fs::read_dir(&vscode_themes_path)? {
|
|
let theme_family_dir = theme_family_dir?;
|
|
|
|
if !theme_family_dir.file_type()?.is_dir() {
|
|
continue;
|
|
}
|
|
|
|
let theme_family_slug = theme_family_dir
|
|
.path()
|
|
.file_stem()
|
|
.ok_or(anyhow!("no file stem"))
|
|
.map(|stem| stem.to_string_lossy().to_string())?;
|
|
|
|
let family_metadata_file = File::open(theme_family_dir.path().join("family.json"))
|
|
.context(format!(
|
|
"no `family.json` found for '{}'",
|
|
theme_family_slug
|
|
))?;
|
|
|
|
let license_file_path = theme_family_dir.path().join("LICENSE");
|
|
|
|
if !license_file_path.exists() {
|
|
println!("Skipping theme family '{}' because it does not have a LICENSE file. This theme will only be imported once a LICENSE file is provided.", theme_family_slug);
|
|
continue;
|
|
}
|
|
|
|
let family_metadata: FamilyMetadata = serde_json::from_reader(family_metadata_file)
|
|
.context(format!(
|
|
"failed to parse `family.json` for '{theme_family_slug}'"
|
|
))?;
|
|
|
|
let mut themes = Vec::new();
|
|
|
|
for theme_metadata in family_metadata.themes {
|
|
let theme_file_path = theme_family_dir.path().join(&theme_metadata.file_name);
|
|
|
|
let theme_file = match File::open(&theme_file_path) {
|
|
Ok(file) => file,
|
|
Err(_) => {
|
|
println!("Failed to open file at path: {:?}", theme_file_path);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
let vscode_theme: VsCodeTheme = serde_json::from_reader(theme_file)
|
|
.context(format!("failed to parse theme {theme_file_path:?}"))?;
|
|
|
|
let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata);
|
|
|
|
let theme = converter.convert()?;
|
|
|
|
themes.push(theme);
|
|
}
|
|
|
|
let theme_family = UserThemeFamily {
|
|
name: family_metadata.name.into(),
|
|
author: family_metadata.author.into(),
|
|
themes,
|
|
};
|
|
|
|
theme_families.push(theme_family);
|
|
}
|
|
|
|
let themes_output_path = PathBuf::from_str(OUT_PATH)?;
|
|
|
|
if !themes_output_path.exists() {
|
|
println!("Creating directory: {:?}", themes_output_path);
|
|
fs::create_dir_all(&themes_output_path)?;
|
|
}
|
|
|
|
let mut mod_rs_file = File::create(themes_output_path.join(format!("mod.rs")))?;
|
|
|
|
let mut theme_modules = Vec::new();
|
|
|
|
for theme_family in theme_families {
|
|
let theme_family_slug = theme_family.name.to_string().to_case(Case::Snake);
|
|
|
|
let mut output_file =
|
|
File::create(themes_output_path.join(format!("{theme_family_slug}.rs")))?;
|
|
println!(
|
|
"Creating file: {:?}",
|
|
themes_output_path.join(format!("{theme_family_slug}.rs"))
|
|
);
|
|
|
|
let theme_module = format!(
|
|
r#"
|
|
// This file was generated by the `theme_importer`.
|
|
// Be careful when modifying it by hand.
|
|
|
|
use gpui::rgba;
|
|
|
|
use crate::{{
|
|
Appearance, ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement,
|
|
}};
|
|
|
|
pub fn {theme_family_slug}() -> UserThemeFamily {{
|
|
{theme_family_definition}
|
|
}}
|
|
"#,
|
|
theme_family_definition = format!("{:#?}", UserThemeFamilyPrinter::new(theme_family))
|
|
);
|
|
|
|
output_file.write_all(theme_module.as_bytes())?;
|
|
|
|
theme_modules.push(theme_family_slug);
|
|
}
|
|
|
|
let themes_vector_contents = format!(
|
|
r#"
|
|
use crate::UserThemeFamily;
|
|
|
|
pub(crate) fn all_user_themes() -> Vec<UserThemeFamily> {{
|
|
vec![{all_themes}]
|
|
}}
|
|
"#,
|
|
all_themes = theme_modules
|
|
.iter()
|
|
.map(|module| format!("{}()", module))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
);
|
|
|
|
let mod_rs_contents = format!(
|
|
r#"
|
|
// This file was generated by the `theme_importer`.
|
|
// Be careful when modifying it by hand.
|
|
|
|
{mod_statements}
|
|
|
|
{use_statements}
|
|
|
|
{themes_vector_contents}
|
|
"#,
|
|
mod_statements = theme_modules
|
|
.iter()
|
|
.map(|module| format!("mod {module};"))
|
|
.collect::<Vec<_>>()
|
|
.join("\n"),
|
|
use_statements = theme_modules
|
|
.iter()
|
|
.map(|module| format!("pub use {module}::*;"))
|
|
.collect::<Vec<_>>()
|
|
.join("\n"),
|
|
themes_vector_contents = themes_vector_contents
|
|
);
|
|
|
|
mod_rs_file.write_all(mod_rs_contents.as_bytes())?;
|
|
|
|
println!("Formatting themes...");
|
|
|
|
let format_result = format_themes_crate()
|
|
// We need to format a second time to catch all of the formatting issues.
|
|
.and_then(|_| format_themes_crate());
|
|
|
|
if let Err(err) = format_result {
|
|
eprintln!("Failed to format themes: {}", err);
|
|
}
|
|
|
|
println!("Done!");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn format_themes_crate() -> std::io::Result<std::process::Output> {
|
|
Command::new("cargo")
|
|
.args(["fmt", "--package", "theme2"])
|
|
.output()
|
|
}
|