main.rs

  1mod assets;
  2mod color;
  3mod vscode;
  4
  5use std::fs::File;
  6use std::io::Write;
  7use std::path::PathBuf;
  8
  9use anyhow::{Context as _, Result};
 10use clap::Parser;
 11use indexmap::IndexMap;
 12use log::LevelFilter;
 13use serde::Deserialize;
 14use simplelog::ColorChoice;
 15use simplelog::{TermLogger, TerminalMode};
 16use theme::{Appearance, AppearanceContent};
 17
 18use crate::vscode::VsCodeTheme;
 19use crate::vscode::VsCodeThemeConverter;
 20
 21const ZED_THEME_SCHEMA_URL: &str = "https://zed.dev/schema/themes/v0.2.0.json";
 22
 23#[derive(Debug, Deserialize)]
 24struct FamilyMetadata {
 25    pub name: String,
 26    pub author: String,
 27    pub themes: Vec<ThemeMetadata>,
 28
 29    /// Overrides for specific syntax tokens.
 30    ///
 31    /// Use this to ensure certain Zed syntax tokens are matched
 32    /// to an exact set of scopes when it is not otherwise possible
 33    /// to rely on the default mappings in the theme importer.
 34    #[serde(default)]
 35    pub syntax: IndexMap<String, Vec<String>>,
 36}
 37
 38#[derive(Debug, Clone, Copy, Deserialize)]
 39#[serde(rename_all = "snake_case")]
 40pub enum ThemeAppearanceJson {
 41    Light,
 42    Dark,
 43}
 44
 45impl From<ThemeAppearanceJson> for AppearanceContent {
 46    fn from(value: ThemeAppearanceJson) -> Self {
 47        match value {
 48            ThemeAppearanceJson::Light => Self::Light,
 49            ThemeAppearanceJson::Dark => Self::Dark,
 50        }
 51    }
 52}
 53
 54impl From<ThemeAppearanceJson> for Appearance {
 55    fn from(value: ThemeAppearanceJson) -> Self {
 56        match value {
 57            ThemeAppearanceJson::Light => Self::Light,
 58            ThemeAppearanceJson::Dark => Self::Dark,
 59        }
 60    }
 61}
 62
 63#[derive(Debug, Deserialize)]
 64pub struct ThemeMetadata {
 65    pub name: String,
 66    pub file_name: String,
 67    pub appearance: ThemeAppearanceJson,
 68}
 69
 70#[derive(Parser)]
 71#[command(author, version, about, long_about = None)]
 72struct Args {
 73    /// The path to the theme to import.
 74    theme_path: PathBuf,
 75
 76    /// Whether to warn when values are missing from the theme.
 77    #[arg(long)]
 78    warn_on_missing: bool,
 79
 80    /// The path to write the output to.
 81    #[arg(long, short)]
 82    output: Option<PathBuf>,
 83}
 84
 85fn main() -> Result<()> {
 86    let args = Args::parse();
 87
 88    let log_config = {
 89        let mut config = simplelog::ConfigBuilder::new();
 90
 91        if !args.warn_on_missing {
 92            config.add_filter_ignore_str("theme_printer");
 93        }
 94
 95        config.build()
 96    };
 97
 98    TermLogger::init(
 99        LevelFilter::Trace,
100        log_config,
101        TerminalMode::Stderr,
102        ColorChoice::Auto,
103    )
104    .expect("could not initialize logger");
105
106    let theme_file_path = args.theme_path;
107
108    let theme_file = match File::open(&theme_file_path) {
109        Ok(file) => file,
110        Err(err) => {
111            log::info!("Failed to open file at path: {:?}", theme_file_path);
112            return Err(err)?;
113        }
114    };
115
116    let vscode_theme: VsCodeTheme = serde_json_lenient::from_reader(theme_file)
117        .context(format!("failed to parse theme {theme_file_path:?}"))?;
118
119    let theme_metadata = ThemeMetadata {
120        name: vscode_theme.name.clone().unwrap_or("".to_string()),
121        appearance: ThemeAppearanceJson::Dark,
122        file_name: "".to_string(),
123    };
124
125    let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::new());
126
127    let theme = converter.convert()?;
128    let mut theme = serde_json::to_value(theme).unwrap();
129    theme.as_object_mut().unwrap().insert(
130        "$schema".to_string(),
131        serde_json::Value::String(ZED_THEME_SCHEMA_URL.to_string()),
132    );
133    let theme_json = serde_json::to_string_pretty(&theme).unwrap();
134
135    if let Some(output) = args.output {
136        let mut file = File::create(output)?;
137        file.write_all(theme_json.as_bytes())?;
138    } else {
139        println!("{}", theme_json);
140    }
141
142    log::info!("Done!");
143
144    Ok(())
145}