Compare commits

..

5 Commits

Author SHA1 Message Date
Tom Alexander
3e952ef0f4
Implement a macro for pushing the directory. 2025-02-08 18:37:32 -05:00
Tom Alexander
4e0f66401d
Update the dependency manager file stack when rendering blog post pages. 2025-02-08 18:01:59 -05:00
Tom Alexander
3867f965d2
Add a dependency manager for render-time actions.
This will be used for supporting things like copying static files or rendering code blocks like gnuplot or graphviz.
2025-02-08 17:29:09 -05:00
Tom Alexander
5cac44c625
Store the path to the original source file in the blog post object. 2025-02-07 21:08:06 -05:00
Tom Alexander
463be34302
Async closure is now stable. 2025-02-07 20:51:31 -05:00
12 changed files with 179 additions and 63 deletions

4
rust-toolchain.toml Normal file
View File

@ -0,0 +1,4 @@
[toolchain]
channel = "nightly"
profile = "default"
components = ["clippy", "rustfmt"]

View File

@ -7,6 +7,7 @@ use tokio::fs::DirEntry;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use crate::config::Config; use crate::config::Config;
use crate::context::DependencyManager;
use crate::context::RenderBlogPostPage; use crate::context::RenderBlogPostPage;
use crate::context::RenderBlogPostPageInput; use crate::context::RenderBlogPostPageInput;
use crate::context::RenderBlogStream; use crate::context::RenderBlogStream;
@ -85,11 +86,14 @@ impl SiteRenderer {
for page in &self.pages { for page in &self.pages {
let output_path = self.output_directory.join(page.get_output_path()); let output_path = self.output_directory.join(page.get_output_path());
let dependency_manager =
std::sync::Arc::new(std::sync::Mutex::new(DependencyManager::new()));
let render_context = RenderContext::new( let render_context = RenderContext::new(
config, config,
self.output_directory.as_path(), self.output_directory.as_path(),
output_path.as_path(), output_path.as_path(),
None, None,
dependency_manager,
)?; )?;
let render_context = RenderPage::new(render_context, page)?; let render_context = RenderPage::new(render_context, page)?;
let rendered_output = renderer_integration.render(render_context)?; let rendered_output = renderer_integration.render(render_context)?;
@ -113,12 +117,15 @@ impl SiteRenderer {
.join(config.get_relative_path_to_post(&blog_post.id)) .join(config.get_relative_path_to_post(&blog_post.id))
.join(blog_post_page.get_output_path()); .join(blog_post_page.get_output_path());
let dependency_manager =
std::sync::Arc::new(std::sync::Mutex::new(DependencyManager::new()));
let convert_input = RenderBlogPostPageInput::new(blog_post, blog_post_page); let convert_input = RenderBlogPostPageInput::new(blog_post, blog_post_page);
let render_context = RenderContext::new( let render_context = RenderContext::new(
config, config,
self.output_directory.as_path(), self.output_directory.as_path(),
output_path.as_path(), output_path.as_path(),
None, None,
dependency_manager,
)?; )?;
let render_context = RenderBlogPostPage::new(render_context, &convert_input)?; let render_context = RenderBlogPostPage::new(render_context, &convert_input)?;
let rendered_output = renderer_integration.render(render_context)?; let rendered_output = renderer_integration.render(render_context)?;
@ -127,6 +134,9 @@ impl SiteRenderer {
.ok_or("Output file should have a containing directory.")?; .ok_or("Output file should have a containing directory.")?;
tokio::fs::create_dir_all(parent_directory).await?; tokio::fs::create_dir_all(parent_directory).await?;
tokio::fs::write(output_path, rendered_output).await?; tokio::fs::write(output_path, rendered_output).await?;
// TODO: Copy post files to output.
// TODO: Update link src to generate path correct for where the page is rendered.
} }
} }
@ -194,12 +204,15 @@ impl SiteRenderer {
)?) )?)
}; };
let dependency_manager =
std::sync::Arc::new(std::sync::Mutex::new(DependencyManager::new()));
let convert_input = RenderBlogStreamInput::new(chunk, older_link, newer_link); let convert_input = RenderBlogStreamInput::new(chunk, older_link, newer_link);
let render_context = RenderContext::new( let render_context = RenderContext::new(
config, config,
self.output_directory.as_path(), self.output_directory.as_path(),
output_file.as_path(), output_file.as_path(),
None, None,
dependency_manager,
)?; )?;
let blog_stream = RenderBlogStream::new(render_context, &convert_input)?; let blog_stream = RenderBlogStream::new(render_context, &convert_input)?;

View File

@ -163,7 +163,7 @@ async fn load_pages(config: &Config) -> Result<Vec<IPage>, CustomError> {
ret.push( ret.push(
IPage::new( IPage::new(
intermediate_context, intermediate_context,
PageInput::new(relative_to_pages_dir_path, parsed_document), PageInput::new(relative_to_pages_dir_path, real_path, parsed_document),
) )
.await?, .await?,
); );

View File

@ -1,6 +1,7 @@
use serde::Serialize; use serde::Serialize;
use super::render_context::RenderContext; use super::render_context::RenderContext;
use crate::context::macros::push_file;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::get_web_path; use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost; use crate::intermediate::BlogPost;
@ -49,76 +50,79 @@ render!(
original, original,
render_context, render_context,
{ {
let css_files = vec![ push_file!(render_context, &original.page.src, {
get_web_path( let css_files = vec![
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"stylesheet/reset.css",
)?,
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"stylesheet/main.css",
)?,
];
let js_files = vec![get_web_path(
render_context.config, render_context.config,
render_context.output_root_directory, render_context.output_root_directory,
render_context.output_file, render_context.output_file,
"stylesheet/reset.css", "blog_post.js",
)?, )?];
get_web_path( let global_settings =
GlobalSettings::new(original.page.title.clone(), css_files, js_files);
let page_header = PageHeader::new(
render_context.config.get_site_title().map(str::to_string),
Some(get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"",
)?),
);
let link_to_blog_post = get_web_path(
render_context.config, render_context.config,
render_context.output_root_directory, render_context.output_root_directory,
render_context.output_file, render_context.output_file,
"stylesheet/main.css", render_context
)?, .output_file
]; .strip_prefix(render_context.output_root_directory)?,
let js_files = vec![get_web_path( )?;
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"blog_post.js",
)?];
let global_settings = GlobalSettings::new(original.page.title.clone(), css_files, js_files);
let page_header = PageHeader::new(
render_context.config.get_site_title().map(str::to_string),
Some(get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"",
)?),
);
let link_to_blog_post = get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
render_context
.output_file
.strip_prefix(render_context.output_root_directory)?,
)?;
let children = { let children = {
let mut children = Vec::new(); let mut children = Vec::new();
for child in original.page.children.iter() { for child in original.page.children.iter() {
children.push(RenderDocumentElement::new(render_context.clone(), child)?); children.push(RenderDocumentElement::new(render_context.clone(), child)?);
} }
children children
}; };
let footnotes = { let footnotes = {
let mut ret = Vec::new(); let mut ret = Vec::new();
for footnote in original.page.footnotes.iter() { for footnote in original.page.footnotes.iter() {
ret.push(RenderRealFootnoteDefinition::new( ret.push(RenderRealFootnoteDefinition::new(
render_context.clone(), render_context.clone(),
footnote, footnote,
)?); )?);
} }
ret ret
}; };
let ret = RenderBlogPostPage { let ret = RenderBlogPostPage {
global_settings, global_settings,
page_header: Some(page_header), page_header: Some(page_header),
title: original.page.title.clone(), title: original.page.title.clone(),
self_link: Some(link_to_blog_post), self_link: Some(link_to_blog_post),
children, children,
footnotes, footnotes,
}; };
Ok(ret) Ok(ret)
})
} }
); );

View File

@ -0,0 +1,36 @@
use std::path::PathBuf;
use crate::error::CustomError;
pub(crate) type RefDependencyManager = std::sync::Arc<std::sync::Mutex<DependencyManager>>;
#[derive(Debug)]
pub(crate) struct DependencyManager {
/// A stack of paths for the files being visited.
///
/// The last entry is the current file being processed. This can be used for handling relative-path links.
file_stack: Vec<PathBuf>,
}
impl DependencyManager {
pub(crate) fn new() -> Self {
DependencyManager {
file_stack: Vec::new(),
}
}
pub(crate) fn push_file<P>(&mut self, path: P) -> Result<(), CustomError>
where
P: Into<PathBuf>,
{
self.file_stack.push(path.into());
Ok(())
}
pub(crate) fn pop_file(&mut self) -> Result<(), CustomError> {
self.file_stack
.pop()
.expect("Popped more files off the dependency manager file stack than exist.");
Ok(())
}
}

View File

@ -35,3 +35,23 @@ macro_rules! rnoop {
} }
pub(crate) use rnoop; pub(crate) use rnoop;
/// Push a file onto the render DependencyManager's file stack while inside the code block.
macro_rules! push_file {
($render_context:ident, $path:expr, $body:tt) => {{
$render_context
.dependency_manager
.lock()
.unwrap()
.push_file($path)?;
let ret = (|| $body)();
$render_context
.dependency_manager
.lock()
.unwrap()
.pop_file()?;
ret
}};
}
pub(crate) use push_file;

View File

@ -11,6 +11,7 @@ mod clock;
mod code; mod code;
mod comment; mod comment;
mod comment_block; mod comment_block;
mod dependency_manager;
mod diary_sexp; mod diary_sexp;
mod document_element; mod document_element;
mod drawer; mod drawer;
@ -72,6 +73,7 @@ pub(crate) use blog_post_page::RenderBlogPostPage;
pub(crate) use blog_post_page::RenderBlogPostPageInput; pub(crate) use blog_post_page::RenderBlogPostPageInput;
pub(crate) use blog_stream::RenderBlogStream; pub(crate) use blog_stream::RenderBlogStream;
pub(crate) use blog_stream::RenderBlogStreamInput; pub(crate) use blog_stream::RenderBlogStreamInput;
pub(crate) use dependency_manager::DependencyManager;
pub(crate) use document_element::RenderDocumentElement; pub(crate) use document_element::RenderDocumentElement;
pub(crate) use element::RenderElement; pub(crate) use element::RenderElement;
pub(crate) use footnote_definition::RenderRealFootnoteDefinition; pub(crate) use footnote_definition::RenderRealFootnoteDefinition;

View File

@ -3,6 +3,8 @@ use std::path::Path;
use crate::config::Config; use crate::config::Config;
use crate::error::CustomError; use crate::error::CustomError;
use super::dependency_manager::RefDependencyManager;
/// The supporting information used for converting the intermediate representation into the dust context for rendering. /// The supporting information used for converting the intermediate representation into the dust context for rendering.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct RenderContext<'intermediate> { pub(crate) struct RenderContext<'intermediate> {
@ -16,6 +18,13 @@ pub(crate) struct RenderContext<'intermediate> {
/// IDs, for example, multiple blog posts with footnotes in a blog /// IDs, for example, multiple blog posts with footnotes in a blog
/// stream. /// stream.
pub(crate) id_addition: Option<&'intermediate str>, pub(crate) id_addition: Option<&'intermediate str>,
/// Tracks dependencies from rendering Org document(s).
///
/// Examples of dependencies would be:
/// - Static files that need to be copied to the output folder
/// - Code blocks that need to be executed (for example, gnuplot graphs)
pub(crate) dependency_manager: RefDependencyManager,
} }
impl<'intermediate> RenderContext<'intermediate> { impl<'intermediate> RenderContext<'intermediate> {
@ -24,12 +33,14 @@ impl<'intermediate> RenderContext<'intermediate> {
output_directory: &'intermediate Path, output_directory: &'intermediate Path,
output_file: &'intermediate Path, output_file: &'intermediate Path,
id_addition: Option<&'intermediate str>, id_addition: Option<&'intermediate str>,
dependency_manager: RefDependencyManager,
) -> Result<RenderContext<'intermediate>, CustomError> { ) -> Result<RenderContext<'intermediate>, CustomError> {
Ok(RenderContext { Ok(RenderContext {
config, config,
output_root_directory: output_directory, output_root_directory: output_directory,
output_file, output_file,
id_addition, id_addition,
dependency_manager,
}) })
} }
} }

View File

@ -35,6 +35,7 @@ impl BlogPost {
) -> Result<BlogPost, CustomError> { ) -> Result<BlogPost, CustomError> {
let post_id = post_dir.strip_prefix(posts_dir)?.as_os_str(); let post_id = post_dir.strip_prefix(posts_dir)?.as_os_str();
// Load all the *.org files under the post directory from disk into memory
let org_files = { let org_files = {
let mut ret = Vec::new(); let mut ret = Vec::new();
let org_files_iter = get_org_files(post_dir).await?; let org_files_iter = get_org_files(post_dir).await?;
@ -43,6 +44,8 @@ impl BlogPost {
} }
ret ret
}; };
// Parse all the *.org files
let parsed_org_files = { let parsed_org_files = {
let mut ret = Vec::new(); let mut ret = Vec::new();
for (path, contents) in org_files.iter() { for (path, contents) in org_files.iter() {
@ -73,7 +76,11 @@ impl BlogPost {
ret.push( ret.push(
BlogPostPage::new( BlogPostPage::new(
intermediate_context, intermediate_context,
BlogPostPageInput::new(relative_to_post_dir_path, parsed_document), BlogPostPageInput::new(
relative_to_post_dir_path,
real_path,
parsed_document,
),
) )
.await?, .await?,
); );

View File

@ -11,17 +11,23 @@ use super::ISection;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct BlogPostPageInput<'b, 'parse> { pub(crate) struct BlogPostPageInput<'b, 'parse> {
/// Relative path from the root of the blog post.
path: PathBuf, path: PathBuf,
/// The path to the .org source for the file.
src: PathBuf,
document: &'b organic::types::Document<'parse>, document: &'b organic::types::Document<'parse>,
} }
impl<'b, 'parse> BlogPostPageInput<'b, 'parse> { impl<'b, 'parse> BlogPostPageInput<'b, 'parse> {
pub(crate) fn new<P: Into<PathBuf>>( pub(crate) fn new<P: Into<PathBuf>, S: Into<PathBuf>>(
path: P, path: P,
src: S,
document: &'b organic::types::Document<'parse>, document: &'b organic::types::Document<'parse>,
) -> BlogPostPageInput<'b, 'parse> { ) -> BlogPostPageInput<'b, 'parse> {
BlogPostPageInput { BlogPostPageInput {
path: path.into(), path: path.into(),
src: src.into(),
document, document,
} }
} }
@ -32,6 +38,9 @@ pub(crate) struct BlogPostPage {
/// Relative path from the root of the blog post. /// Relative path from the root of the blog post.
pub(crate) path: PathBuf, pub(crate) path: PathBuf,
/// The path to the .org source for the file.
pub(crate) src: PathBuf,
pub(crate) title: Option<String>, pub(crate) title: Option<String>,
pub(crate) date: Option<String>, pub(crate) date: Option<String>,
@ -79,6 +88,7 @@ intermediate!(
Ok(BlogPostPage { Ok(BlogPostPage {
path: original.path, path: original.path,
src: original.src,
title: get_title(original.document), title: get_title(original.document),
date: get_date(original.document), date: get_date(original.document),
children, children,

View File

@ -13,6 +13,9 @@ pub(crate) struct IPage {
/// Relative path from the root of the pages directory. /// Relative path from the root of the pages directory.
pub(crate) path: PathBuf, pub(crate) path: PathBuf,
/// The path to the .org source for the file.
pub(crate) src: PathBuf,
pub(crate) title: Option<String>, pub(crate) title: Option<String>,
#[allow(dead_code)] #[allow(dead_code)]
@ -61,6 +64,7 @@ intermediate!(
Ok(IPage { Ok(IPage {
path: original.path, path: original.path,
src: original.src,
title: get_title(original.document), title: get_title(original.document),
date: get_date(original.document), date: get_date(original.document),
children, children,
@ -80,17 +84,23 @@ impl IPage {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PageInput<'b, 'parse> { pub(crate) struct PageInput<'b, 'parse> {
/// Relative path from the root of the page.
path: PathBuf, path: PathBuf,
/// The path to the .org source for the file.
src: PathBuf,
document: &'b organic::types::Document<'parse>, document: &'b organic::types::Document<'parse>,
} }
impl<'b, 'parse> PageInput<'b, 'parse> { impl<'b, 'parse> PageInput<'b, 'parse> {
pub(crate) fn new<P: Into<PathBuf>>( pub(crate) fn new<P: Into<PathBuf>, S: Into<PathBuf>>(
path: P, path: P,
src: S,
document: &'b organic::types::Document<'parse>, document: &'b organic::types::Document<'parse>,
) -> PageInput<'b, 'parse> { ) -> PageInput<'b, 'parse> {
PageInput { PageInput {
path: path.into(), path: path.into(),
src: src.into(),
document, document,
} }
} }

View File

@ -1,5 +1,4 @@
#![feature(let_chains)] #![feature(let_chains)]
#![feature(async_closure)]
use std::process::ExitCode; use std::process::ExitCode;
use clap::Parser; use clap::Parser;