Compare commits
10 Commits
a510d0809f
...
586fd8a066
Author | SHA1 | Date | |
---|---|---|---|
![]() |
586fd8a066 | ||
![]() |
043cc5eda4 | ||
![]() |
58aba8efd5 | ||
![]() |
ce0819e85b | ||
![]() |
fc5342adce | ||
![]() |
aed88cf05a | ||
![]() |
24bac982f1 | ||
![]() |
5f34cb2dd5 | ||
![]() |
0b6900eeca | ||
![]() |
b72aec9d20 |
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -216,6 +216,25 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "include_dir"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e"
|
||||
dependencies = [
|
||||
"include_dir_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir_macros"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.2"
|
||||
@ -624,6 +643,7 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"duster",
|
||||
"include_dir",
|
||||
"organic",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -6,14 +6,12 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# error-context, suggestions, usage | env
|
||||
clap = { version = "4.4.6", default-features = false, features = ["std", "color", "help", "derive"] }
|
||||
duster = { git = "https://code.fizz.buzz/talexander/duster.git", branch = "master" }
|
||||
include_dir = "0.7.3"
|
||||
organic = "0.1.12"
|
||||
# | alloc, rc, serde_derive, unstable
|
||||
serde = { version = "1.0.189", default-features = false, features = ["std", "derive"] }
|
||||
serde_json = "1.0.107"
|
||||
tokio = { version = "1.30.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util"] }
|
||||
# display, parse | indexmap, preserve_order
|
||||
toml = "0.8.2"
|
||||
walkdir = "2.4.0"
|
||||
|
9
default_environment/templates/html/main.dust
Normal file
9
default_environment/templates/html/main.dust
Normal file
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello world!</p>
|
||||
</body>
|
||||
</html>
|
8
src/blog_post/convert.rs
Normal file
8
src/blog_post/convert.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use super::render_context::RenderBlogPost;
|
||||
use super::BlogPost;
|
||||
|
||||
pub(crate) fn convert_blog_post_to_render_context(post: &BlogPost) -> RenderBlogPost {
|
||||
RenderBlogPost {
|
||||
id: post.id.clone(),
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ use crate::error::CustomError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BlogPost {
|
||||
id: String,
|
||||
pub(crate) id: String,
|
||||
}
|
||||
|
||||
impl BlogPost {
|
||||
|
@ -1,2 +1,5 @@
|
||||
mod convert;
|
||||
mod definition;
|
||||
mod render_context;
|
||||
pub(crate) use convert::convert_blog_post_to_render_context;
|
||||
pub(crate) use definition::BlogPost;
|
||||
|
8
src/blog_post/render_context.rs
Normal file
8
src/blog_post/render_context.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename = "blog_post")]
|
||||
pub(crate) struct RenderBlogPost {
|
||||
pub(crate) id: String,
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
mod render;
|
||||
mod runner;
|
||||
|
||||
pub(crate) use runner::build_site;
|
||||
|
78
src/command/build/render.rs
Normal file
78
src/command/build/render.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use include_dir::include_dir;
|
||||
use include_dir::Dir;
|
||||
|
||||
use crate::blog_post::convert_blog_post_to_render_context;
|
||||
use crate::blog_post::BlogPost;
|
||||
use crate::error::CustomError;
|
||||
use crate::render::DusterRenderer;
|
||||
use crate::render::RendererIntegration;
|
||||
|
||||
static MAIN_TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/default_environment/templates/html");
|
||||
|
||||
pub(crate) struct SiteRenderer {
|
||||
output_directory: PathBuf,
|
||||
blog_posts: Vec<BlogPost>,
|
||||
}
|
||||
|
||||
impl SiteRenderer {
|
||||
pub(crate) fn new<P: Into<PathBuf>>(
|
||||
output_directory: P,
|
||||
blog_posts: Vec<BlogPost>,
|
||||
) -> SiteRenderer {
|
||||
SiteRenderer {
|
||||
output_directory: output_directory.into(),
|
||||
blog_posts,
|
||||
}
|
||||
}
|
||||
pub(crate) async fn render_blog_posts(&self) -> Result<(), CustomError> {
|
||||
let mut renderer_integration = DusterRenderer::new();
|
||||
|
||||
let (main_template, other_templates): (Vec<_>, Vec<_>) = MAIN_TEMPLATES
|
||||
.files()
|
||||
.filter(|f| f.path().extension() == Some(OsStr::new("dust")))
|
||||
.partition(|f| f.path().file_stem() == Some(OsStr::new("main")));
|
||||
if main_template.len() != 1 {
|
||||
return Err("Expect exactly 1 main.dust template file.".into());
|
||||
}
|
||||
|
||||
let decoded_templates = {
|
||||
let mut decoded_templates =
|
||||
Vec::with_capacity(main_template.len() + other_templates.len());
|
||||
for entry in main_template {
|
||||
decoded_templates.push(build_name_contents_pairs(entry)?);
|
||||
}
|
||||
for entry in other_templates {
|
||||
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 {
|
||||
let render_context = convert_blog_post_to_render_context(blog_post);
|
||||
let rendered_output = renderer_integration.render(render_context)?;
|
||||
println!("Rendered: {}", rendered_output);
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
@ -2,22 +2,16 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::blog_post::BlogPost;
|
||||
use crate::cli::parameters::BuildArgs;
|
||||
use crate::command::build::render::SiteRenderer;
|
||||
use crate::config::Config;
|
||||
use crate::error::CustomError;
|
||||
|
||||
pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> {
|
||||
let config = Config::load_from_file(args.config).await?;
|
||||
let root_directory = config.get_root_directory().to_owned();
|
||||
let output_directory = get_output_directory(&config).await?;
|
||||
let post_directories = get_post_directories(&config).await?;
|
||||
let load_jobs = post_directories
|
||||
.into_iter()
|
||||
.map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path)));
|
||||
let mut blog_posts = Vec::new();
|
||||
for job in load_jobs {
|
||||
blog_posts.push(job.await??);
|
||||
}
|
||||
println!("{:?}", blog_posts);
|
||||
let blog_posts = load_blog_posts(&config).await?;
|
||||
let renderer = SiteRenderer::new(get_output_directory(&config).await?, blog_posts);
|
||||
renderer.render_blog_posts().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -51,3 +45,16 @@ async fn get_post_directories(config: &Config) -> Result<Vec<PathBuf>, CustomErr
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
async fn load_blog_posts(config: &Config) -> Result<Vec<BlogPost>, CustomError> {
|
||||
let root_directory = config.get_root_directory().to_owned();
|
||||
let post_directories = get_post_directories(&config).await?;
|
||||
let load_jobs = post_directories
|
||||
.into_iter()
|
||||
.map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path)));
|
||||
let mut blog_posts = Vec::new();
|
||||
for job in load_jobs {
|
||||
blog_posts.push(job.await??);
|
||||
}
|
||||
Ok(blog_posts)
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
use std::str::Utf8Error;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum CustomError {
|
||||
Static(&'static str),
|
||||
@ -6,6 +9,11 @@ pub(crate) enum CustomError {
|
||||
TomlDeserialize(toml::de::Error),
|
||||
WalkDir(walkdir::Error),
|
||||
Tokio(tokio::task::JoinError),
|
||||
Serde(serde_json::Error),
|
||||
Utf8(Utf8Error),
|
||||
FromUtf8(FromUtf8Error),
|
||||
DusterCompile(duster::renderer::CompileError),
|
||||
DusterRender(duster::renderer::RenderError),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for CustomError {
|
||||
@ -43,3 +51,33 @@ impl From<tokio::task::JoinError> for CustomError {
|
||||
CustomError::Tokio(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for CustomError {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
CustomError::Serde(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for CustomError {
|
||||
fn from(value: Utf8Error) -> Self {
|
||||
CustomError::Utf8(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for CustomError {
|
||||
fn from(value: FromUtf8Error) -> Self {
|
||||
CustomError::FromUtf8(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<duster::renderer::CompileError> for CustomError {
|
||||
fn from(value: duster::renderer::CompileError) -> Self {
|
||||
CustomError::DusterCompile(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<duster::renderer::RenderError> for CustomError {
|
||||
fn from(value: duster::renderer::RenderError) -> Self {
|
||||
CustomError::DusterRender(value)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ mod cli;
|
||||
mod command;
|
||||
mod config;
|
||||
mod error;
|
||||
mod render;
|
||||
|
||||
fn main() -> Result<ExitCode, CustomError> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
|
42
src/render/duster_renderer.rs
Normal file
42
src/render/duster_renderer.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::error::CustomError;
|
||||
|
||||
use super::renderer_integration::RendererIntegration;
|
||||
use serde::Serialize;
|
||||
|
||||
pub(crate) struct DusterRenderer<'a> {
|
||||
templates: HashMap<&'a str, duster::parser::Template<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> DusterRenderer<'a> {
|
||||
pub(crate) fn new() -> DusterRenderer<'a> {
|
||||
DusterRenderer {
|
||||
templates: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RendererIntegration<'a> for DusterRenderer<'a> {
|
||||
fn load_template(&mut self, name: &'a str, contents: &'a str) -> Result<(), CustomError> {
|
||||
let compiled_template = duster::renderer::compile_template(contents.as_ref())?;
|
||||
self.templates.insert(name, compiled_template);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render<C>(&self, context: C) -> Result<String, CustomError>
|
||||
where
|
||||
C: Serialize,
|
||||
{
|
||||
let mut dust_renderer = duster::renderer::DustRenderer::new();
|
||||
for (name, compiled_template) in self.templates.iter() {
|
||||
dust_renderer.load_source(compiled_template, (*name).to_owned());
|
||||
}
|
||||
// TODO: This is horribly inefficient. I am converting from a serialize type to json and back again so I can use the existing implementation of IntoContextElement. Honestly, I probably need to rework a lot of duster now that I've improved in rust over the years.
|
||||
let json_context = serde_json::to_string(&context)?;
|
||||
println!("Context: {}", json_context);
|
||||
let parsed_context: serde_json::Value = serde_json::from_str(json_context.as_str())?;
|
||||
let rendered_output = dust_renderer.render("main", Some(&parsed_context))?;
|
||||
Ok(rendered_output)
|
||||
}
|
||||
}
|
4
src/render/mod.rs
Normal file
4
src/render/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod duster_renderer;
|
||||
mod renderer_integration;
|
||||
pub(crate) use duster_renderer::DusterRenderer;
|
||||
pub(crate) use renderer_integration::RendererIntegration;
|
11
src/render/renderer_integration.rs
Normal file
11
src/render/renderer_integration.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::error::CustomError;
|
||||
|
||||
pub(crate) trait RendererIntegration<'a> {
|
||||
fn load_template(&mut self, name: &'a str, contents: &'a str) -> Result<(), CustomError>;
|
||||
|
||||
fn render<C>(&self, context: C) -> Result<String, CustomError>
|
||||
where
|
||||
C: Serialize;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user