use crate::parser::Document;
use crate::theme::Theme;
use crate::i18n::I18nManager;
use crate::site::SiteConfig;
use crate::error::{KrikResult, KrikError, ThemeError, ThemeErrorKind};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tracing::{info, debug, warn, error};
#[derive(Debug)]
pub struct SiteGenerator {
pub source_dir: PathBuf,
pub output_dir: PathBuf,
pub theme: Theme,
pub i18n: I18nManager,
pub site_config: SiteConfig,
pub documents: Vec<Document>,
}
impl SiteGenerator {
pub fn new<P: AsRef<Path>>(
source_dir: P,
output_dir: P,
theme_dir: Option<P>,
) -> KrikResult<Self> {
let source_dir = source_dir.as_ref().to_path_buf();
let output_dir = output_dir.as_ref().to_path_buf();
let theme = if let Some(theme_path) = theme_dir {
let path = theme_path.as_ref().to_path_buf();
Theme::load_from_path(&path)
.map_err(|_| KrikError::Theme(ThemeError {
kind: ThemeErrorKind::NotFound,
theme_path: path.clone(),
context: format!("Loading custom theme from {}", path.display()),
}))?
} else {
let default_path = PathBuf::from("themes/default");
Theme::load_from_path(&default_path).unwrap_or_else(|_| {
Theme {
config: crate::theme::ThemeConfig {
name: "default".to_string(),
version: "1.0.0".to_string(),
author: None,
description: None,
templates: HashMap::new(),
},
templates: tera::Tera::new("themes/default/templates/**/*").unwrap_or_default(),
theme_path: default_path,
}
})
};
let i18n = I18nManager::new("en".to_string());
let site_config = SiteConfig::load_from_path(&source_dir);
Ok(Self {
source_dir,
output_dir,
theme,
i18n,
site_config,
documents: Vec::new(),
})
}
pub fn scan_files(&mut self) -> KrikResult<()> {
info!("Scanning for markdown files in: {}", self.source_dir.display());
let result = super::markdown::scan_files(&self.source_dir, &mut self.documents)
.map_err(|e| match e {
KrikError::Generation(gen_err) => KrikError::Generation(gen_err),
other => other,
});
match &result {
Ok(_) => info!("Successfully scanned {} documents", self.documents.len()),
Err(e) => error!("Failed to scan files: {}", e),
}
result
}
pub fn generate_site(&self) -> KrikResult<()> {
use super::pipeline::{EmitPhase, RenderPhase, ScanPhase, TransformPhase};
info!("Starting site generation");
debug!("Source directory: {}", self.source_dir.display());
debug!("Output directory: {}", self.output_dir.display());
let scan = ScanPhase;
let transform = TransformPhase;
let render = RenderPhase;
let emit = EmitPhase;
info!("Preparing output directory");
emit.ensure_output_dir(&self.output_dir)?;
info!("Scanning source files");
let mut documents = scan.scan(&self.source_dir)?;
debug!("Found {} documents to process", documents.len());
info!("Transforming documents");
transform.transform(&mut documents, &self.source_dir);
info!("Copying assets");
emit.copy_assets(&self.source_dir, &self.theme, &self.output_dir)?;
info!("Rendering pages");
render.render_pages(&documents, &self.theme, &self.i18n, &self.site_config, &self.output_dir)?;
render.render_index(&documents, &self.theme, &self.site_config, &self.output_dir)?;
info!("Generating ancillary files");
emit.emit_feed(&documents, &self.site_config, &self.output_dir)?;
emit.emit_sitemap(&documents, &self.site_config, &self.output_dir)?;
emit.emit_robots(&self.site_config, &self.output_dir)?;
if super::pdf::PdfGenerator::is_available() {
info!("PDF generation tools available, generating PDFs");
match super::pdf::PdfGenerator::new() {
Ok(pdf_generator) => {
match pdf_generator.generate_pdfs(&documents, &self.source_dir, &self.output_dir, &self.site_config) {
Ok(generated_pdfs) => {
if !generated_pdfs.is_empty() {
info!("Generated {} PDF files alongside their HTML counterparts", generated_pdfs.len());
}
}
Err(e) => warn!("PDF generation failed: {}", e),
}
}
Err(e) => warn!("Could not initialize PDF generator: {}", e),
}
} else {
debug!("PDF generation skipped: pandoc and/or typst not available in PATH");
}
info!("Site generation completed successfully");
Ok(())
}
}