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::ColorChoice;
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 /// The path to write the output to.
80 #[arg(long, short)]
81 output: Option<PathBuf>,
82
83 #[command(subcommand)]
84 command: Option<Command>,
85}
86
87#[derive(Subcommand)]
88enum Command {
89 /// Prints the JSON schema for a theme.
90 PrintSchema,
91}
92
93fn main() -> Result<()> {
94 let args = Args::parse();
95
96 let log_config = {
97 let mut config = simplelog::ConfigBuilder::new();
98
99 if !args.warn_on_missing {
100 config.add_filter_ignore_str("theme_printer");
101 }
102
103 config.build()
104 };
105
106 TermLogger::init(
107 LevelFilter::Trace,
108 log_config,
109 TerminalMode::Stderr,
110 ColorChoice::Auto,
111 )
112 .expect("could not initialize logger");
113
114 if let Some(command) = args.command {
115 match command {
116 Command::PrintSchema => {
117 let theme_family_schema = schema_for!(ThemeFamilyContent);
118
119 println!(
120 "{}",
121 serde_json::to_string_pretty(&theme_family_schema).unwrap()
122 );
123
124 return Ok(());
125 }
126 }
127 }
128
129 let theme_file_path = args.theme_path;
130
131 let theme_file = match File::open(&theme_file_path) {
132 Ok(file) => file,
133 Err(err) => {
134 log::info!("Failed to open file at path: {:?}", theme_file_path);
135 return Err(err)?;
136 }
137 };
138
139 let vscode_theme: VsCodeTheme = serde_json_lenient::from_reader(theme_file)
140 .context(format!("failed to parse theme {theme_file_path:?}"))?;
141
142 let theme_metadata = ThemeMetadata {
143 name: vscode_theme.name.clone().unwrap_or("".to_string()),
144 appearance: ThemeAppearanceJson::Dark,
145 file_name: "".to_string(),
146 };
147
148 let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::new());
149
150 let theme = converter.convert()?;
151
152 let theme_json = serde_json::to_string_pretty(&theme).unwrap();
153
154 if let Some(output) = args.output {
155 let mut file = File::create(output)?;
156 file.write_all(theme_json.as_bytes())?;
157 } else {
158 println!("{}", theme_json);
159 }
160
161 log::info!("Done!");
162
163 Ok(())
164}