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 collections::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, Clone, Copy, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum ThemeAppearanceJson {
25 Light,
26 Dark,
27}
28
29impl From<ThemeAppearanceJson> for AppearanceContent {
30 fn from(value: ThemeAppearanceJson) -> Self {
31 match value {
32 ThemeAppearanceJson::Light => Self::Light,
33 ThemeAppearanceJson::Dark => Self::Dark,
34 }
35 }
36}
37
38impl From<ThemeAppearanceJson> for Appearance {
39 fn from(value: ThemeAppearanceJson) -> Self {
40 match value {
41 ThemeAppearanceJson::Light => Self::Light,
42 ThemeAppearanceJson::Dark => Self::Dark,
43 }
44 }
45}
46
47#[derive(Debug, Deserialize)]
48pub struct ThemeMetadata {
49 pub name: String,
50 pub file_name: String,
51 pub appearance: ThemeAppearanceJson,
52}
53
54#[derive(Parser)]
55#[command(author, version, about, long_about = None)]
56struct Args {
57 /// The path to the theme to import.
58 theme_path: PathBuf,
59
60 /// Whether to warn when values are missing from the theme.
61 #[arg(long)]
62 warn_on_missing: bool,
63
64 /// The path to write the output to.
65 #[arg(long, short)]
66 output: Option<PathBuf>,
67}
68
69fn main() -> Result<()> {
70 let args = Args::parse();
71
72 let log_config = {
73 let mut config = simplelog::ConfigBuilder::new();
74
75 if !args.warn_on_missing {
76 config.add_filter_ignore_str("theme_printer");
77 }
78
79 config.build()
80 };
81
82 TermLogger::init(
83 LevelFilter::Trace,
84 log_config,
85 TerminalMode::Stderr,
86 ColorChoice::Auto,
87 )
88 .expect("could not initialize logger");
89
90 let theme_file_path = args.theme_path;
91
92 let theme_file = match File::open(&theme_file_path) {
93 Ok(file) => file,
94 Err(err) => {
95 log::info!("Failed to open file at path: {:?}", theme_file_path);
96 return Err(err)?;
97 }
98 };
99
100 let vscode_theme: VsCodeTheme = serde_json_lenient::from_reader(theme_file)
101 .context(format!("failed to parse theme {theme_file_path:?}"))?;
102
103 let theme_metadata = ThemeMetadata {
104 name: vscode_theme.name.clone().unwrap_or("".to_string()),
105 appearance: ThemeAppearanceJson::Dark,
106 file_name: "".to_string(),
107 };
108
109 let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::default());
110
111 let theme = converter.convert()?;
112 let mut theme = serde_json::to_value(theme).unwrap();
113 theme.as_object_mut().unwrap().insert(
114 "$schema".to_string(),
115 serde_json::Value::String(ZED_THEME_SCHEMA_URL.to_string()),
116 );
117 let theme_json = serde_json::to_string_pretty(&theme).unwrap();
118
119 if let Some(output) = args.output {
120 let mut file = File::create(output)?;
121 file.write_all(theme_json.as_bytes())?;
122 } else {
123 println!("{}", theme_json);
124 }
125
126 log::info!("Done!");
127
128 Ok(())
129}