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