main.rs

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