1use super::{Error, Result};
2use std::io::Read;
3use std::path::PathBuf;
4
5use serde_derive::{Deserialize, Serialize};
6
7#[derive(Debug, Serialize, Deserialize)]
8#[serde(default, rename_all = "kebab-case")]
9pub struct Ace {
10 pub theme_white: String,
11 pub theme_dark: String,
12 pub below_build_dir: bool,
13 pub build_dir: PathBuf, pub theme_dir: PathBuf, pub destination: PathBuf, }
17
18impl Ace {
19 pub fn css_class_text(&self, dark: bool) -> Result<(String, String)> {
26 let mut css_text = String::new();
27 let ace_file = format!("ace-{}.css", if dark { "dark" } else { "white" });
28 let path = self.theme_dir.join(ace_file);
29
30 if path.exists() || self.theme_dir.join("ace.css").exists() {
31 std::fs::File::open(path)
32 .unwrap()
33 .read_to_string(&mut css_text)
34 .unwrap();
35 } else if let Some(v) = self.defult_css(dark) {
36 css_text = String::from(unsafe { std::str::from_utf8_unchecked(v) });
37 } else {
38 return Err(Error::AceNotFound);
39 }
40
41 css_text = css_text.replace(['\n', '"'], " ");
42 let p1 = css_text.find(".ace-").ok_or(Error::StrNotFound)?;
43 let css_class =
44 css_text[p1 + 1..p1 + css_text[p1..].find(' ').ok_or(Error::StrNotFound)?].to_string();
45 Ok((css_class, css_text))
46 }
47
48 #[rustfmt::skip]
50 pub fn defult_css(&self, dark: bool) -> Option<&[u8]> {
51 ACE_DEFAULT.iter()
52 .find(|&(path, _)| {
53 if dark { path.strip_suffix(".css").unwrap().ends_with(&self.theme_dark) }
54 else { path.strip_suffix(".css").unwrap().ends_with(&self.theme_white) }
55 })
56 .map(|&(_, bytes)| bytes)
57 }
58
59 pub fn write(&self, css_: (String, String), dark: bool) -> Result<()> {
61 let file = if dark {
62 "theme-tomorrow_night.js"
63 } else {
64 "theme-dawn.js"
65 };
66 let path = &self.build_dir.join("html").join(file);
67
68 let (css_class, css_text) = css_;
69 let mut content = String::new();
70 std::fs::File::open(path)
71 .unwrap()
72 .read_to_string(&mut content)
73 .unwrap();
74 content.replace_range(find(&content, "cssClass=\"")?, &css_class);
75 content.replace_range(find(&content, "cssText=\"")?, &css_text);
76
77 std::fs::write(path, content).map_err(|_| Error::FileNotWritten)?;
78 Ok(())
79 }
80
81 pub fn run(self) -> Result<()> {
83 for dark in [true, false] {
84 self.write(self.css_class_text(dark)?, dark)?;
85 }
86 self.below_build_dir()?;
87 self.remove_destination();
88 Ok(())
89 }
90
91 pub fn below_build_dir(&self) -> Result<()> {
93 if self.below_build_dir {
94 use mdbook::utils::fs::copy_files_except_ext as copy;
95 let html = self.build_dir.join("html");
96 copy(&html, &self.build_dir, true, None, &[]).map_err(|_| Error::DirNotCreated)?;
97 std::fs::remove_dir_all(html).map_err(|_| Error::DirNotRemoved)?;
98 }
99 Ok(())
100 }
101
102 fn remove_destination(&self) {
105 std::fs::remove_dir(&self.destination).unwrap_or_default();
106 }
107}
108
109impl Default for Ace {
110 fn default() -> Self {
111 Self {
112 theme_white: String::from(""),
113 theme_dark: String::from(""),
114 build_dir: PathBuf::from(""),
115 theme_dir: PathBuf::from(""),
116 destination: PathBuf::from(""),
117 below_build_dir: true,
118 }
119 }
120}
121
122fn find(content: &str, target: &str) -> Result<std::ops::Range<usize>> {
125 let p1 = content.find(target).ok_or(Error::StrNotFound)? + target.len();
126 let p2 = p1 + content[p1..].find('"').ok_or(Error::StrNotFound)?;
127 Ok(p1..p2)
128}
129
130default! {
131 "./ace/theme/ambiance.css", AMBIANCE;
132 "./ace/theme/chaos.css", CHAOS;
133 "./ace/theme/chrome.css", CHROME;
134 "./ace/theme/clouds.css", CLOUDS;
135 "./ace/theme/clouds_midnight.css", CLOUDS_MIDNIGHT;
136 "./ace/theme/cobalt.css", COBALT;
137 "./ace/theme/crimson_editor.css", CRIMSON_EDITOR;
138 "./ace/theme/dawn.css", DAWN;
139 "./ace/theme/dracula.css", DRACULA;
140 "./ace/theme/dreamweaver.css", DREAMWEAVER;
141 "./ace/theme/eclipse.css", ECLIPSE;
142 "./ace/theme/github.css", GITHUB;
143 "./ace/theme/gob.css", GOB;
144 "./ace/theme/gruvbox.css", GRUVBOX;
145 "./ace/theme/idle_fingers.css", IDLE_FINGERS;
146 "./ace/theme/iplastic.css", IPLASTIC;
147 "./ace/theme/katzenmilch.css", KATZENMILCH;
148 "./ace/theme/kr_theme.css", KR_THEME;
149 "./ace/theme/kuroir.css", KUROIR;
150 "./ace/theme/merbivore.css", MERBIVORE;
151 "./ace/theme/merbivore_soft.css", MERBIVORE_SOFT;
152 "./ace/theme/mono_industrial.css", MONO_INDUSTRIAL;
153 "./ace/theme/monokai.css", MONOKAI;
154 "./ace/theme/nord_dark.css", NORD_DARK;
155 "./ace/theme/one_dark.css", ONE_DARK;
156 "./ace/theme/pastel_on_dark.css", PASTEL_ON_DARK;
157 "./ace/theme/solarized_dark.css", SOLARIZED_DARK;
158 "./ace/theme/solarized_light.css", SOLARIZED_LIGHT;
159 "./ace/theme/sqlserver.css", SQLSERVER;
160 "./ace/theme/terminal.css", TERMINAL;
161 "./ace/theme/textmate.css", TEXTMATE;
162 "./ace/theme/tomorrow.css", TOMORROW;
163 "./ace/theme/tomorrow_night_blue.css", TOMORROW_NIGHT_BLUE;
164 "./ace/theme/tomorrow_night_bright.css", TOMORROW_NIGHT_BRIGHT;
165 "./ace/theme/tomorrow_night.css", TOMORROW_NIGHT;
166 "./ace/theme/tomorrow_night_eighties.css", TOMORROW_NIGHT_EIGHTIES;
167 "./ace/theme/twilight.css", TWILIGHT;
168 "./ace/theme/vibrant_ink.css", VIBRANT_INK;
169 "./ace/theme/xcode.css", XCODE
170}