natter/src/command/build/render.rs

138 lines
4.6 KiB
Rust
Raw Normal View History

use std::ffi::OsStr;
use std::path::PathBuf;
use include_dir::include_dir;
use include_dir::Dir;
2023-10-23 21:51:15 -04:00
use crate::config::Config;
use crate::error::CustomError;
use crate::intermediate::convert_blog_post_page_to_render_context;
use crate::intermediate::BlogPost;
use crate::render::DusterRenderer;
2023-10-22 16:40:58 -04:00
use crate::render::RendererIntegration;
use super::stylesheet::Stylesheet;
static MAIN_TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/default_environment/templates/html");
pub(crate) struct SiteRenderer {
output_directory: PathBuf,
blog_posts: Vec<BlogPost>,
stylesheets: Vec<Stylesheet>,
}
impl SiteRenderer {
pub(crate) fn new<P: Into<PathBuf>>(
output_directory: P,
blog_posts: Vec<BlogPost>,
stylesheets: Vec<Stylesheet>,
) -> SiteRenderer {
SiteRenderer {
output_directory: output_directory.into(),
blog_posts,
stylesheets,
}
}
2023-10-23 21:51:15 -04:00
pub(crate) async fn render_blog_posts(&self, config: &Config) -> Result<(), CustomError> {
let mut renderer_integration = DusterRenderer::new();
let sources: Vec<_> = MAIN_TEMPLATES
.files()
.filter(|f| f.path().extension() == Some(OsStr::new("dust")))
.collect();
if sources
.iter()
.filter(|f| f.path().file_stem() == Some(OsStr::new("main")))
.count()
!= 1
{
return Err("Expect exactly 1 main.dust template file.".into());
}
let decoded_templates = {
let mut decoded_templates = Vec::with_capacity(sources.len());
for entry in sources {
decoded_templates.push(build_name_contents_pairs(entry)?);
}
decoded_templates
};
for (name, contents) in decoded_templates {
renderer_integration.load_template(name, contents)?;
}
for blog_post in &self.blog_posts {
for blog_post_page in &blog_post.pages {
let output_path = self
.output_directory
.join("posts")
.join(&blog_post.id)
.join(blog_post_page.get_output_path());
let render_context = convert_blog_post_page_to_render_context(
2023-10-23 21:51:15 -04:00
config,
&self.output_directory,
&output_path,
blog_post,
blog_post_page,
)?;
let rendered_output = renderer_integration.render(render_context)?;
let parent_directory = output_path
.parent()
.ok_or("Output file should have a containing directory.")?;
tokio::fs::create_dir_all(parent_directory).await?;
tokio::fs::write(output_path, rendered_output).await?;
}
}
Ok(())
}
pub(crate) async fn render_blog_stream(&self, config: &Config) -> Result<(), CustomError> {
// TODO: Actually render a blog stream to index.html
// Steps: sort blog posts by date, newest first
//
// Steps: group blog posts based on # of posts per page
//
// Steps: for each group, create a RenderBlogStream
//
// Steps: pass each RenderBlogStream to dust as the context to render index.html and any additional stream pages.
2023-12-17 15:45:50 -05:00
for blog_post in &self.blog_posts {
println!("{:?}", blog_post.get_date()?);
}
Ok(())
}
pub(crate) async fn render_stylesheets(&self) -> Result<(), CustomError> {
let stylesheet_output_directory = self.output_directory.join("stylesheet");
if !stylesheet_output_directory.exists() {
tokio::fs::create_dir(&stylesheet_output_directory).await?;
}
for stylesheet in &self.stylesheets {
let file_output_path = stylesheet_output_directory.join(&stylesheet.path);
let parent_directory = file_output_path
.parent()
.ok_or("Output file should have a containing directory.")?;
tokio::fs::create_dir_all(parent_directory).await?;
tokio::fs::write(file_output_path, stylesheet.contents.as_bytes()).await?;
}
Ok(())
}
}
fn build_name_contents_pairs<'a>(
entry: &'a include_dir::File<'_>,
) -> Result<(&'a str, &'a str), CustomError> {
let path = entry.path();
let name = path
.file_stem()
.ok_or("All templates should have a stem.")?
.to_str()
.ok_or("All template filenames should be valid utf-8.")?;
let contents = std::str::from_utf8(entry.contents())?;
Ok((name, contents))
}