use crate::project::config::Mode;
use anyhow::{anyhow, Context as AContext};
use cdoc::config::Format;
use cdoc::renderers::RenderResult;
use cdoc::templates::{TemplateManager, TemplateType};
use cdoc_parser::document::Document;
use indicatif::{ParallelProgressIterator, ProgressBar};
use rayon::prelude::*;
use std::fs;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::ops::Deref;
use std::path::PathBuf;
use tera::Context;
use crate::project::config::ProjectConfig;
use crate::project::{ContentItemDescriptor, ContentResultX, ProjectItemContentVec};
pub struct Generator<'a> {
pub root: PathBuf,
pub project: &'a ContentResultX,
pub templates: &'a TemplateManager,
pub config: ProjectConfig,
pub mode: Mode,
pub build_dir: PathBuf,
pub format: &'a dyn Format,
}
impl Generator<'_> {
fn get_writer(
&self,
doc_id: &str,
doc_path: &PathBuf,
is_section: bool,
) -> anyhow::Result<impl Write> {
let relative_doc_path = doc_path
.strip_prefix(self.root.join("content").as_path())
.unwrap_or(doc_path);
let mut html_build_dir = self.build_dir.join(relative_doc_path);
html_build_dir.pop();
let id = if is_section { "index" } else { doc_id };
let section_build_path = html_build_dir.join(format!("{}.{}", id, self.format.extension()));
fs::create_dir_all(&html_build_dir).with_context(|| {
format!(
"Could not create directory at: {}",
html_build_dir.display()
)
})?;
let file = File::create(§ion_build_path)
.with_context(|| format!("{}", section_build_path.display()))?;
let writer = BufWriter::new(file);
Ok(writer)
}
pub fn generate(
&self,
bar: ProgressBar,
project_vec: &ProjectItemContentVec,
) -> anyhow::Result<Vec<anyhow::Result<String>>> {
if self.format.include_resources() {
let resource_path_src = self.root.join("resources");
let resource_path_build_dir = self.build_dir.as_path().join("resources");
fs::create_dir_all(resource_path_build_dir.as_path())?;
let mut options = fs_extra::dir::CopyOptions::new();
options.overwrite = true;
fs_extra::copy_items(&[&resource_path_src], self.build_dir.as_path(), &options)
.with_context(|| {
format!(
"from {} to {}",
resource_path_src.display(),
self.build_dir.as_path().display()
)
})?;
}
let mut base = Context::new();
base.insert("project", &self.project);
base.insert("config", &self.config);
let res = project_vec
.par_iter()
.progress_with(bar)
.map(|item| {
if let Some(c) = item.doc.content.deref() {
self.process(&base, c, item)?;
}
Ok(item.doc.path.to_str().unwrap().to_string())
})
.collect();
Ok(res)
}
pub fn process<T>(
&self,
args: &Context,
doc: &Document<RenderResult>,
item: &ContentItemDescriptor<T>,
) -> anyhow::Result<()> {
if !(self.mode == Mode::Release && doc.meta.draft) {
let mut writer = self
.get_writer(&item.doc.id, &item.doc.path, item.is_section)
.with_context(|| {
format!(
"Could not create writer for document at path: {}",
item.doc.path.display()
)
})?;
if let Some(layout_id) = self.format.layout() {
let mut args = args.clone();
args.insert("current_path", &item.path);
args.insert("doc", &doc);
args.insert("mode", &self.mode);
self.templates.render(
&layout_id,
self.format.template_prefix(),
TemplateType::Layout,
&args,
&mut writer,
)?;
} else {
let bytes = doc.content.as_bytes();
let l = writer.write(bytes)?;
(l == bytes.len())
.then_some(())
.ok_or(anyhow!("did not write the correct amount of bytes"))?;
};
}
Ok(())
}
pub fn generate_single(
&mut self,
doc: &Document<RenderResult>,
doc_info: &ContentItemDescriptor<()>,
) -> anyhow::Result<()> {
let mut base = Context::new();
base.insert("project", &self.project);
base.insert("config", &self.config);
self.process(&base, doc, doc_info)
}
}