use crate::error::{KrikError, KrikResult, TemplateError, TemplateErrorKind};
use crate::parser::Document;
use crate::site::SiteConfig;
use crate::theme::Theme;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use tera::Context;
use super::context::{
add_language_context, add_navigation_context, add_page_links_context, add_sidebar_context,
add_site_context, generate_description,
};
use super::paths::determine_output_path;
use super::select::determine_template_name;
use rayon::prelude::*;
use std::sync::Mutex;
pub fn generate_pages(
documents: &[Document],
theme: &Theme,
site_config: &SiteConfig,
output_dir: &Path,
) -> KrikResult<()> {
let first_error: Mutex<Option<KrikError>> = Mutex::new(None);
documents.par_iter().for_each(|document| {
if let Err(e) = generate_page(document, documents, theme, site_config, output_dir) {
if let Ok(mut guard) = first_error.lock() {
if guard.is_none() {
*guard = Some(e);
}
}
}
});
if let Ok(Some(err)) = first_error.into_inner() {
return Err(err);
}
Ok(())
}
pub fn generate_page(
document: &Document,
all_documents: &[Document],
theme: &Theme,
site_config: &SiteConfig,
output_dir: &Path,
) -> KrikResult<()> {
let context = build_page_context(document, all_documents, site_config);
let rendered_content = render_template(theme, document, &context)?;
write_output_file(document, output_dir, &rendered_content)
}
pub fn build_page_context(
document: &Document,
all_documents: &[Document],
site_config: &SiteConfig,
) -> Context {
let mut context = create_base_context(document);
add_processed_content(&mut context, document);
add_all_contexts(&mut context, document, all_documents, site_config);
context
}
pub fn create_base_context(document: &Document) -> Context {
let mut context = Context::new();
context.insert("title", &document.front_matter.title);
context.insert("content", &document.content);
context.insert("date", &document.front_matter.date);
context.insert("tags", &document.front_matter.tags);
context.insert("language", &document.language);
context.insert("base_name", &document.base_name);
context.insert("pdf", &document.front_matter.pdf);
let frontmatter_desc = document
.front_matter
.extra
.get("description")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let description = generate_description(&document.content, frontmatter_desc.as_ref());
context.insert("description", &description);
for (key, value) in &document.front_matter.extra {
context.insert(key, value);
}
context
}
pub fn add_processed_content(context: &mut Context, document: &Document) {
let content_without_title = crate::generator::markdown::remove_duplicate_title(
&document.content,
document.front_matter.title.as_deref(),
);
context.insert("content", &content_without_title);
if let Some(toc_html) = &document.toc {
context.insert("toc", toc_html);
}
let processed_content = crate::generator::markdown::process_footnotes(
context
.get("content")
.and_then(|v| v.as_str())
.unwrap_or(""),
);
context.insert("content", &processed_content);
}
pub fn add_all_contexts(
context: &mut Context,
document: &Document,
all_documents: &[Document],
site_config: &SiteConfig,
) {
add_site_context(
context,
site_config,
&document.language,
&document.file_path,
);
add_navigation_context(context, document);
add_language_context(context, document, all_documents);
add_sidebar_context(context, all_documents);
add_page_links_context(context, all_documents, &document.file_path);
}
pub fn render_template(
theme: &Theme,
document: &Document,
context: &Context,
) -> KrikResult<String> {
let template_name = determine_template_name(document);
theme
.templates
.render(&template_name, context)
.map_err(|e| {
KrikError::Template(Box::new(TemplateError {
kind: TemplateErrorKind::RenderError(e),
template: template_name.clone(),
context: format!("Rendering page for {}", document.file_path),
}))
})
}
pub fn write_output_file(
document: &Document,
output_dir: &Path,
rendered_content: &str,
) -> KrikResult<()> {
let output_path = determine_output_path(&document.file_path, output_dir);
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut file = File::create(&output_path)?;
file.write_all(rendered_content.as_bytes())?;
Ok(())
}