use std::collections::HashMap;
use std::fmt::Write;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::rc::Rc;
use anyhow::anyhow;
use log::{debug, warn};
use serde::{Serialize, Deserialize};
use tempfile::{tempdir, TempDir};
use crate::input::Config;
use crate::markdown::RenderedContent;
const MAIN_JS: &str = include_str!("../res/js/main.js");
const MAIN_CSS: &str = include_str!("../res/style/main.css");
const GITHUB_CSS: &str = include_str!("../res/style/github.css");
pub const HIGHLIGHT_JS_VERSION: &str = "9.18.1";
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PageState {
pub scroll_top: f64,
pub image_widths: HashMap<String, f64>,
pub image_heights: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct Assets {
real_dir: Option<PathBuf>,
temp_dir: Option<Rc<TempDir>>,
}
impl Assets {
pub fn init(output_dir: Option<PathBuf>) -> Result<Self, io::Error> {
let assets =
if let Some(real_dir) = output_dir {
if !real_dir.is_dir() {
fs::create_dir_all(&real_dir)?;
}
let real_dir = Some(real_dir.canonicalize()?);
Assets { real_dir, temp_dir: None }
} else {
let temp_dir = tempdir()?;
let temp_dir = Some(Rc::new(temp_dir));
Assets { temp_dir, real_dir: None }
};
let output_path = assets.output_path().unwrap();
fs::write(output_path.join("main.js"), MAIN_JS).
unwrap_or_else(|e| warn!("{}", e));
fs::write(output_path.join("main.css"), MAIN_CSS).
unwrap_or_else(|e| warn!("{}", e));
fs::write(output_path.join("github.css"), GITHUB_CSS).
unwrap_or_else(|e| warn!("{}", e));
Ok(assets)
}
pub fn build(&self, content: &RenderedContent, page_state: &PageState) -> anyhow::Result<PathBuf> {
let output_path = self.output_path()?;
let custom_css_path = Config::css_path();
let json_state = serde_json::to_string(page_state).
unwrap_or_else(|e| {
warn!("Couldn't build JSON state from {:?}: {:?}", page_state, e);
String::from("{}")
});
let mut hl_tags = String::new();
if !content.code_languages.is_empty() {
let root_url = format!(
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/{}",
HIGHLIGHT_JS_VERSION
);
writeln!(hl_tags, r#"<link rel="stylesheet" href="{}/styles/github.min.css" />"#, root_url).
unwrap();
writeln!(hl_tags, r#"<script src="{}/highlight.min.js"></script>"#, root_url).
unwrap();
for language in &content.code_languages {
writeln!(
hl_tags,
r#"<script src="{}/languages/{}.min.js"></script>"#,
root_url, language
).unwrap();
}
writeln!(hl_tags, r#"<script>hljs.initHighlighting()</script>"#).unwrap();
}
debug!("Building HTML:");
debug!(" > custom_css_path = {:?}", custom_css_path);
debug!(" > page_state = {:?}", json_state);
debug!(" > code languages = {:?}", content.code_languages);
let page = format! {
include_str!("../res/layout.html"),
custom_css_path = custom_css_path.display(),
body = content.html,
hl_tags = hl_tags,
page_state = json_state,
};
let html_path = output_path.join("index.html");
fs::write(&html_path, page.as_bytes())?;
Ok(html_path)
}
pub fn output_path(&self) -> anyhow::Result<PathBuf> {
match (&self.real_dir, &self.temp_dir) {
(Some(path_buf), _) => Ok(path_buf.clone()),
(_, Some(temp_dir)) => Ok(temp_dir.path().to_path_buf()),
_ => Err(anyhow!("Assets don't have an output dir, there might be a synchronization error"))
}
}
pub fn clean_up(&mut self) {
if let Some(temp_dir) = self.temp_dir.take() {
let path = temp_dir.path();
fs::remove_dir_all(path).unwrap_or_else(|_| {
warn!("Couldn't delete temporary dir: {}", path.display());
});
}
}
}