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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
@ -624,6 +643,7 @@ version = "0.0.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"duster",
|
"duster",
|
||||||
|
"include_dir",
|
||||||
"organic",
|
"organic",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -6,14 +6,12 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# error-context, suggestions, usage | env
|
|
||||||
clap = { version = "4.4.6", default-features = false, features = ["std", "color", "help", "derive"] }
|
clap = { version = "4.4.6", default-features = false, features = ["std", "color", "help", "derive"] }
|
||||||
duster = { git = "https://code.fizz.buzz/talexander/duster.git", branch = "master" }
|
duster = { git = "https://code.fizz.buzz/talexander/duster.git", branch = "master" }
|
||||||
|
include_dir = "0.7.3"
|
||||||
organic = "0.1.12"
|
organic = "0.1.12"
|
||||||
# | alloc, rc, serde_derive, unstable
|
|
||||||
serde = { version = "1.0.189", default-features = false, features = ["std", "derive"] }
|
serde = { version = "1.0.189", default-features = false, features = ["std", "derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
tokio = { version = "1.30.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util"] }
|
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"
|
toml = "0.8.2"
|
||||||
walkdir = "2.4.0"
|
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)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct BlogPost {
|
pub(crate) struct BlogPost {
|
||||||
id: String,
|
pub(crate) id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlogPost {
|
impl BlogPost {
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
|
mod convert;
|
||||||
mod definition;
|
mod definition;
|
||||||
|
mod render_context;
|
||||||
|
pub(crate) use convert::convert_blog_post_to_render_context;
|
||||||
pub(crate) use definition::BlogPost;
|
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;
|
mod runner;
|
||||||
|
|
||||||
pub(crate) use runner::build_site;
|
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::blog_post::BlogPost;
|
||||||
use crate::cli::parameters::BuildArgs;
|
use crate::cli::parameters::BuildArgs;
|
||||||
|
use crate::command::build::render::SiteRenderer;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
|
|
||||||
pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> {
|
pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> {
|
||||||
let config = Config::load_from_file(args.config).await?;
|
let config = Config::load_from_file(args.config).await?;
|
||||||
let root_directory = config.get_root_directory().to_owned();
|
let blog_posts = load_blog_posts(&config).await?;
|
||||||
let output_directory = get_output_directory(&config).await?;
|
let renderer = SiteRenderer::new(get_output_directory(&config).await?, blog_posts);
|
||||||
let post_directories = get_post_directories(&config).await?;
|
renderer.render_blog_posts().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);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,3 +45,16 @@ async fn get_post_directories(config: &Config) -> Result<Vec<PathBuf>, CustomErr
|
|||||||
}
|
}
|
||||||
Ok(ret)
|
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)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum CustomError {
|
pub(crate) enum CustomError {
|
||||||
Static(&'static str),
|
Static(&'static str),
|
||||||
@ -6,6 +9,11 @@ pub(crate) enum CustomError {
|
|||||||
TomlDeserialize(toml::de::Error),
|
TomlDeserialize(toml::de::Error),
|
||||||
WalkDir(walkdir::Error),
|
WalkDir(walkdir::Error),
|
||||||
Tokio(tokio::task::JoinError),
|
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 {
|
impl From<std::io::Error> for CustomError {
|
||||||
@ -43,3 +51,33 @@ impl From<tokio::task::JoinError> for CustomError {
|
|||||||
CustomError::Tokio(value)
|
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 command;
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod render;
|
||||||
|
|
||||||
fn main() -> Result<ExitCode, CustomError> {
|
fn main() -> Result<ExitCode, CustomError> {
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
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