use axoasset::LocalAsset;
use camino::{Utf8Path, Utf8PathBuf};
use mdbook::MDBook;
use std::path::PathBuf;
use crate::config::MdBookConfig;
use crate::data::workspaces::WorkspaceData;
use crate::errors::*;
use crate::site::{oranda_theme::OrandaTheme, Site};
use super::markdown::SyntaxTheme;
use crate::paths::determine_path;
const THEME_GENERAL_CSS_PATH: &str = "css/general.css";
const THEME_GENERAL_CSS: &str = include_str!("../../oranda-css/mdbook-theme/css/general.css");
const THEME_VARIABLES_CSS_PATH: &str = "css/variables.css";
const THEME_VARIABLES_CSS: &str = include_str!("../../oranda-css/mdbook-theme/css/variables.css");
const THEME_CHROME_CSS_PATH: &str = "css/chrome.css";
const THEME_CHROME_CSS: &str = include_str!("../../oranda-css/mdbook-theme/css/chrome.css");
const THEME_FONTS_CSS_PATH: &str = "fonts/fonts.css";
const THEME_FONTS_CSS: &str = include_str!("../../oranda-css/mdbook-theme/fonts/fonts.css");
const THEME_BOOK_JS_PATH: &str = "book.js";
const THEME_BOOK_JS: &str = include_str!("../../oranda-css/mdbook-theme/book.js");
const THEME_INDEX_HBS_PATH: &str = "index.hbs";
const THEME_INDEX_HBS: &str = include_str!("../../oranda-css/mdbook-theme/index.hbs");
const KEY_ORANDA_VARS: &str = "/*ORANDA-THEME-VARS*/";
const KEY_ORANDA_BUTTONS: &str = "<!--ORANDA-THEME-BUTTONS-->";
const KEY_BUTTON_ID: &str = "{{THEME-ID}}";
const KEY_BUTTON_NAME: &str = "{{THEME-NAME}}";
const THEME_BUTTON_HTML_TEMPLATE: &str = r#" <li role="none"><button role="menuitem" class="theme" id="{{THEME-ID}}">{{THEME-NAME}}</button></li>"#;
const CLASS_ORANDA_DARK: &str = "oranda-dark";
const CLASS_ORANDA_LIGHT: &str = "oranda-light";
const THEME_IMPL_DEFAULT: &str =
include_str!("../../oranda-css/mdbook-theme/oranda-themes/default.css");
const THEME_IMPL_AXO: &str = include_str!("../../oranda-css/mdbook-theme/oranda-themes/axo.css");
const THEME_IMPL_HACKER: &str =
include_str!("../../oranda-css/mdbook-theme/oranda-themes/hacker.css");
const THEME_IMPL_CUPCAKE: &str =
include_str!("../../oranda-css/mdbook-theme/oranda-themes/cupcake.css");
const MDBOOK_THEMES: &[(AxomdbookTheme, &str)] = &[
(AxomdbookTheme::Default, THEME_IMPL_DEFAULT),
(AxomdbookTheme::DefaultLight, THEME_IMPL_DEFAULT),
(AxomdbookTheme::AxoDark, THEME_IMPL_AXO),
(AxomdbookTheme::AxoLight, THEME_IMPL_AXO),
(AxomdbookTheme::Hacker, THEME_IMPL_HACKER),
(AxomdbookTheme::Cupcake, THEME_IMPL_CUPCAKE),
];
const THEME_AXO_HIGHLIGHT_CSS_PATH: &str = "oranda-highlight.css";
const SYNTAX_THEMES: &[(SyntaxTheme, &str)] = &[(
SyntaxTheme::MaterialTheme,
include_str!("../../oranda-css/mdbook-theme/highlight-js-themes/base16-material.css"),
)];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AxomdbookTheme {
Default,
DefaultLight,
AxoDark,
AxoLight,
Hacker,
Cupcake,
}
impl AxomdbookTheme {
pub fn from_oranda_theme(oranda_theme: &OrandaTheme) -> Option<Self> {
use AxomdbookTheme::*;
match oranda_theme {
OrandaTheme::Light => Some(DefaultLight),
OrandaTheme::Dark => Some(Default),
OrandaTheme::AxoDark => Some(AxoDark),
OrandaTheme::AxoLight => Some(AxoLight),
OrandaTheme::Hacker => Some(Hacker),
OrandaTheme::Cupcake => Some(Cupcake),
}
}
pub fn is_dark(&self) -> bool {
use AxomdbookTheme::*;
match self {
Default => true,
DefaultLight => false,
AxoDark => true,
AxoLight => false,
Hacker => true,
Cupcake => false,
}
}
pub fn twin_theme(&self) -> Option<AxomdbookTheme> {
use AxomdbookTheme::*;
match self {
Default => Some(DefaultLight),
DefaultLight => Some(Default),
AxoDark => Some(AxoLight),
AxoLight => Some(AxoDark),
Hacker => None,
Cupcake => None,
}
}
pub fn class(&self) -> &'static str {
if self.is_dark() {
CLASS_ORANDA_DARK
} else {
CLASS_ORANDA_LIGHT
}
}
pub fn name(&self) -> &'static str {
use AxomdbookTheme::*;
match self {
Default => "Oranda Dark",
DefaultLight => "Oranda Light",
AxoDark => "Axo Dark",
AxoLight => "Axo Light",
Hacker => "Hacker",
Cupcake => "Cupcake",
}
}
}
pub fn mdbook_dir(
workspace: Option<&WorkspaceData>,
book_cfg: &MdBookConfig,
) -> Result<Utf8PathBuf> {
let root_path = Utf8PathBuf::from_path_buf(std::env::current_dir()?).unwrap_or_default();
let member_path = workspace.map(|w| &w.path);
let book_path = book_cfg
.path
.as_ref()
.expect("Had no mdbook.path, but config code didn't disable mdbook?");
let path = determine_path(root_path, &member_path, book_path)?;
if let Some(path) = path {
Ok(path)
} else {
Err(OrandaError::PathDoesNotExist {
path: book_path.clone(),
})
}
}
pub fn custom_theme(book_cfg: &MdBookConfig, oranda_theme: &OrandaTheme) -> Option<AxomdbookTheme> {
if book_cfg.theme {
AxomdbookTheme::from_oranda_theme(oranda_theme)
} else {
None
}
}
pub fn custom_theme_dir(_book_cfg: &MdBookConfig, dist: &Utf8Path) -> Result<Utf8PathBuf> {
let pwd = axoasset::LocalAsset::current_dir()?;
Ok(pwd.join(dist).join("mdbook_theme"))
}
pub fn build_mdbook(
workspace: Option<&WorkspaceData>,
dist: &Utf8Path,
book_cfg: &MdBookConfig,
oranda_theme: &OrandaTheme,
syntax_theme: &SyntaxTheme,
) -> Result<()> {
let book_dir = mdbook_dir(workspace, book_cfg)?;
let mut md = load_mdbook(&book_dir)?;
let book_src = homogenize_path(&md.source_dir());
let book_dest = homogenize_path(&md.config.build.build_dir.clone());
if book_dest.starts_with(&book_src) {
return Err(OrandaError::MdbookBuildRecursive {
src_path: book_src.display().to_string(),
dest_path: book_dest.display().to_string(),
});
}
let custom_theme = custom_theme(book_cfg, oranda_theme);
let theme_dir = custom_theme_dir(book_cfg, dist)?;
if let Some(theme) = custom_theme {
init_theme_dir(&theme_dir, theme)?;
let dark_theme = theme;
md.config
.set("output.html.default-theme", theme.class())
.expect("failed to convert theme name to a TOML String");
md.config
.set("output.html.preferred-dark-theme", dark_theme.class())
.expect("failed to convert dark theme name to a TOML String");
md.config
.set("output.html.theme", &theme_dir)
.expect("failed to convert theme_dir to a TOML String");
}
let build_dir =
Utf8PathBuf::from_path_buf(md.build_dir_for("html")).expect("mdbook path wasn't utf8");
md.build().map_err(|e| OrandaError::MdBookBuild {
path: book_dir.to_string(),
details: e,
})?;
if custom_theme.is_some() {
add_custom_syntax_theme_to_output(syntax_theme, &build_dir)?;
delete_theme_dir(&theme_dir)?;
}
let book_dist = dist.join("book");
Site::copy_static(&book_dist, build_dir.as_str())?;
Ok(())
}
pub fn load_mdbook(book_dir: &Utf8Path) -> Result<MDBook> {
let path = book_dir.canonicalize_utf8().unwrap_or(book_dir.to_owned());
let md = MDBook::load(&path).map_err(|e| OrandaError::MdBookLoad {
path: path.to_string(),
details: e,
})?;
Ok(md)
}
pub fn homogenize_path(path: &PathBuf) -> PathBuf {
if path.is_relative() && !path.starts_with("./") {
return PathBuf::from(".").join(path);
}
path.to_owned()
}
fn init_theme_dir(theme_dir: &Utf8Path, theme: AxomdbookTheme) -> Result<()> {
delete_theme_dir(theme_dir)?;
let theme_vars = MDBOOK_THEMES
.iter()
.find_map(
|(t, contents)| {
if t == &theme {
Some(*contents)
} else {
None
}
},
)
.expect("failed to find axomdbook theme for mdbook!?");
let variables = THEME_VARIABLES_CSS.replace(KEY_ORANDA_VARS, theme_vars);
let mut buttons = String::new();
add_theme_button(&mut buttons, theme);
if let Some(twin) = theme.twin_theme() {
add_theme_button(&mut buttons, twin);
}
let index = THEME_INDEX_HBS.replace(KEY_ORANDA_BUTTONS, &buttons);
let files = vec![
(THEME_GENERAL_CSS_PATH, THEME_GENERAL_CSS),
(THEME_VARIABLES_CSS_PATH, &variables),
(THEME_CHROME_CSS_PATH, THEME_CHROME_CSS),
(THEME_FONTS_CSS_PATH, THEME_FONTS_CSS),
(THEME_BOOK_JS_PATH, THEME_BOOK_JS),
(THEME_INDEX_HBS_PATH, &index),
];
for (subpath, contents) in files {
let path = theme_dir.join(subpath);
LocalAsset::write_new_all(contents, path)?;
}
Ok(())
}
fn delete_theme_dir(theme_dir: &Utf8Path) -> Result<()> {
LocalAsset::remove_dir_all(theme_dir.as_str())?;
Ok(())
}
fn add_custom_syntax_theme_to_output(
syntax_theme: &SyntaxTheme,
build_dir: &Utf8Path,
) -> Result<()> {
let highlight_theme = SYNTAX_THEMES
.iter()
.find_map(|(theme, contents)| {
if theme == syntax_theme {
Some(*contents)
} else {
None
}
})
.expect("failed to find highlightjs syntax theme for mdbook!?");
LocalAsset::write_new_all(
highlight_theme,
build_dir.join(THEME_AXO_HIGHLIGHT_CSS_PATH),
)?;
Ok(())
}
fn add_theme_button(output: &mut String, theme: AxomdbookTheme) {
let id = theme.class();
let name = theme.name();
let button = THEME_BUTTON_HTML_TEMPLATE
.replace(KEY_BUTTON_ID, id)
.replace(KEY_BUTTON_NAME, name);
output.push_str(&button);
output.push('\n');
}