main.rs

  1mod color;
  2mod vscode;
  3
  4use std::fs::File;
  5use std::io::Write;
  6use std::path::PathBuf;
  7
  8use anyhow::{Context as _, Result};
  9use clap::Parser;
 10use indexmap::IndexMap;
 11use log::LevelFilter;
 12use serde::Deserialize;
 13use simplelog::ColorChoice;
 14use simplelog::{TermLogger, TerminalMode};
 15use theme::{Appearance, AppearanceContent};
 16
 17use crate::vscode::VsCodeTheme;
 18use crate::vscode::VsCodeThemeConverter;
 19
 20const ZED_THEME_SCHEMA_URL: &str = "https://zed.dev/schema/themes/v0.2.0.json";
 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    /// The path to write the output to.
 80    #[arg(long, short)]
 81    output: Option<PathBuf>,
 82}
 83
 84fn main() -> Result<()> {
 85    let args = Args::parse();
 86
 87    let log_config = {
 88        let mut config = simplelog::ConfigBuilder::new();
 89
 90        if !args.warn_on_missing {
 91            config.add_filter_ignore_str("theme_printer");
 92        }
 93
 94        config.build()
 95    };
 96
 97    TermLogger::init(
 98        LevelFilter::Trace,
 99        log_config,
100        TerminalMode::Stderr,
101        ColorChoice::Auto,
102    )
103    .expect("could not initialize logger");
104
105    let theme_file_path = args.theme_path;
106
107    let theme_file = match File::open(&theme_file_path) {
108        Ok(file) => file,
109        Err(err) => {
110            log::info!("Failed to open file at path: {:?}", theme_file_path);
111            return Err(err)?;
112        }
113    };
114
115    let vscode_theme: VsCodeTheme = serde_json_lenient::from_reader(theme_file)
116        .context(format!("failed to parse theme {theme_file_path:?}"))?;
117
118    let theme_metadata = ThemeMetadata {
119        name: vscode_theme.name.clone().unwrap_or("".to_string()),
120        appearance: ThemeAppearanceJson::Dark,
121        file_name: "".to_string(),
122    };
123
124    let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::new());
125
126    let theme = converter.convert()?;
127    let mut theme = serde_json::to_value(theme).unwrap();
128    theme.as_object_mut().unwrap().insert(
129        "$schema".to_string(),
130        serde_json::Value::String(ZED_THEME_SCHEMA_URL.to_string()),
131    );
132    let theme_json = serde_json::to_string_pretty(&theme).unwrap();
133
134    if let Some(output) = args.output {
135        let mut file = File::create(output)?;
136        file.write_all(theme_json.as_bytes())?;
137    } else {
138        println!("{}", theme_json);
139    }
140
141    log::info!("Done!");
142
143    Ok(())
144}