Merge branch 'render_images'
This commit is contained in:
commit
79c36476bd
@ -32,7 +32,8 @@
|
||||
{@eq value="code"}{>code/}{/eq}
|
||||
{@eq value="verbatim"}{>verbatim/}{/eq}
|
||||
{@eq value="plain_text"}{>plain_text/}{/eq}
|
||||
{@eq value="regular_link"}{>regular_link/}{/eq}
|
||||
{@eq value="regular_link_anchor"}{>regular_link_anchor/}{/eq}
|
||||
{@eq value="regular_link_image"}{>regular_link_image/}{/eq}
|
||||
{@eq value="radio_link"}{>radio_link/}{/eq}
|
||||
{@eq value="radio_target"}{>radio_target/}{/eq}
|
||||
{@eq value="plain_link"}{>plain_link/}{/eq}
|
||||
|
@ -6,7 +6,8 @@
|
||||
{@eq value="code"}{>code/}{/eq}
|
||||
{@eq value="verbatim"}{>verbatim/}{/eq}
|
||||
{@eq value="plain_text"}{>plain_text/}{/eq}
|
||||
{@eq value="regular_link"}{>regular_link/}{/eq}
|
||||
{@eq value="regular_link_anchor"}{>regular_link_anchor/}{/eq}
|
||||
{@eq value="regular_link_image"}{>regular_link_image/}{/eq}
|
||||
{@eq value="radio_link"}{>radio_link/}{/eq}
|
||||
{@eq value="radio_target"}{>radio_target/}{/eq}
|
||||
{@eq value="plain_link"}{>plain_link/}{/eq}
|
||||
|
@ -0,0 +1 @@
|
||||
<img src="{.src}" alt="{.alt}" />
|
22
org_test_documents/regular_link/image_links.org
Normal file
22
org_test_documents/regular_link/image_links.org
Normal file
@ -0,0 +1,22 @@
|
||||
[[file:image.svg]]
|
||||
|
||||
[[file:/image.svg]]
|
||||
|
||||
[[file:./image.svg]]
|
||||
|
||||
[[/image.svg]]
|
||||
|
||||
[[./image.svg]]
|
||||
|
||||
# Check capitalization of extension
|
||||
[[./image.SVG]]
|
||||
|
||||
# Check spaces in path
|
||||
[[./image and stuff.SVG]]
|
||||
|
||||
[[/ssh:admin@test.example:important/file.svg]]
|
||||
|
||||
[[file:/ssh:admin@test.example:important/file.svg]]
|
||||
|
||||
# Check multiple parts in the path
|
||||
[[file:/foo/bar/baz/image.svg]]
|
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
profile = "default"
|
||||
components = ["clippy", "rustfmt"]
|
@ -7,6 +7,7 @@ use tokio::fs::DirEntry;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::DependencyManager;
|
||||
use crate::context::RenderBlogPostPage;
|
||||
use crate::context::RenderBlogPostPageInput;
|
||||
use crate::context::RenderBlogStream;
|
||||
@ -85,19 +86,27 @@ impl SiteRenderer {
|
||||
|
||||
for page in &self.pages {
|
||||
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(
|
||||
config,
|
||||
self.output_directory.as_path(),
|
||||
output_path.as_path(),
|
||||
None,
|
||||
dependency_manager.clone(),
|
||||
)?;
|
||||
let render_context = RenderPage::new(render_context, page)?;
|
||||
let rendered_output = renderer_integration.render(render_context)?;
|
||||
let dust_context = RenderPage::new(render_context.clone(), page)?;
|
||||
let rendered_output = renderer_integration.render(dust_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?;
|
||||
tokio::fs::write(&output_path, rendered_output).await?;
|
||||
|
||||
let dependencies = dependency_manager.lock().unwrap().take_dependencies();
|
||||
for dependency in dependencies {
|
||||
dependency.perform(render_context.clone()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -113,20 +122,28 @@ impl SiteRenderer {
|
||||
.join(config.get_relative_path_to_post(&blog_post.id))
|
||||
.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 render_context = RenderContext::new(
|
||||
config,
|
||||
self.output_directory.as_path(),
|
||||
output_path.as_path(),
|
||||
None,
|
||||
dependency_manager.clone(),
|
||||
)?;
|
||||
let render_context = RenderBlogPostPage::new(render_context, &convert_input)?;
|
||||
let rendered_output = renderer_integration.render(render_context)?;
|
||||
let dust_context = RenderBlogPostPage::new(render_context.clone(), &convert_input)?;
|
||||
let rendered_output = renderer_integration.render(dust_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?;
|
||||
tokio::fs::write(&output_path, rendered_output).await?;
|
||||
|
||||
let dependencies = dependency_manager.lock().unwrap().take_dependencies();
|
||||
for dependency in dependencies {
|
||||
dependency.perform(render_context.clone()).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,14 +211,17 @@ 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 render_context = RenderContext::new(
|
||||
config,
|
||||
self.output_directory.as_path(),
|
||||
output_file.as_path(),
|
||||
None,
|
||||
dependency_manager.clone(),
|
||||
)?;
|
||||
let blog_stream = RenderBlogStream::new(render_context, &convert_input)?;
|
||||
let blog_stream = RenderBlogStream::new(render_context.clone(), &convert_input)?;
|
||||
|
||||
// Pass each RenderBlogStream to dust as the context to render index.html and any additional stream pages.
|
||||
let rendered_output = renderer_integration.render(blog_stream)?;
|
||||
@ -209,7 +229,12 @@ impl SiteRenderer {
|
||||
.parent()
|
||||
.ok_or("Output file should have a containing directory.")?;
|
||||
tokio::fs::create_dir_all(parent_directory).await?;
|
||||
tokio::fs::write(output_file, rendered_output).await?;
|
||||
tokio::fs::write(&output_file, rendered_output).await?;
|
||||
|
||||
let dependencies = dependency_manager.lock().unwrap().take_dependencies();
|
||||
for dependency in dependencies {
|
||||
dependency.perform(render_context.clone()).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ async fn load_pages(config: &Config) -> Result<Vec<IPage>, CustomError> {
|
||||
ret.push(
|
||||
IPage::new(
|
||||
intermediate_context,
|
||||
PageInput::new(relative_to_pages_dir_path, parsed_document),
|
||||
PageInput::new(relative_to_pages_dir_path, real_path, parsed_document),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
use serde::Serialize;
|
||||
|
||||
use super::render_context::RenderContext;
|
||||
use crate::context::macros::push_file;
|
||||
use crate::error::CustomError;
|
||||
use crate::intermediate::get_web_path;
|
||||
use crate::intermediate::BlogPost;
|
||||
@ -49,76 +50,79 @@ render!(
|
||||
original,
|
||||
render_context,
|
||||
{
|
||||
let css_files = vec![
|
||||
get_web_path(
|
||||
push_file!(render_context, &original.page.src, {
|
||||
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.output_root_directory,
|
||||
render_context.output_file,
|
||||
"stylesheet/reset.css",
|
||||
)?,
|
||||
get_web_path(
|
||||
"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,
|
||||
"stylesheet/main.css",
|
||||
)?,
|
||||
];
|
||||
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)?,
|
||||
)?;
|
||||
render_context
|
||||
.output_file
|
||||
.strip_prefix(render_context.output_root_directory)?,
|
||||
)?;
|
||||
|
||||
let children = {
|
||||
let mut children = Vec::new();
|
||||
let children = {
|
||||
let mut children = Vec::new();
|
||||
|
||||
for child in original.page.children.iter() {
|
||||
children.push(RenderDocumentElement::new(render_context.clone(), child)?);
|
||||
}
|
||||
for child in original.page.children.iter() {
|
||||
children.push(RenderDocumentElement::new(render_context.clone(), child)?);
|
||||
}
|
||||
|
||||
children
|
||||
};
|
||||
children
|
||||
};
|
||||
|
||||
let footnotes = {
|
||||
let mut ret = Vec::new();
|
||||
let footnotes = {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for footnote in original.page.footnotes.iter() {
|
||||
ret.push(RenderRealFootnoteDefinition::new(
|
||||
render_context.clone(),
|
||||
footnote,
|
||||
)?);
|
||||
}
|
||||
for footnote in original.page.footnotes.iter() {
|
||||
ret.push(RenderRealFootnoteDefinition::new(
|
||||
render_context.clone(),
|
||||
footnote,
|
||||
)?);
|
||||
}
|
||||
|
||||
ret
|
||||
};
|
||||
ret
|
||||
};
|
||||
|
||||
let ret = RenderBlogPostPage {
|
||||
global_settings,
|
||||
page_header: Some(page_header),
|
||||
title: original.page.title.clone(),
|
||||
self_link: Some(link_to_blog_post),
|
||||
children,
|
||||
footnotes,
|
||||
};
|
||||
Ok(ret)
|
||||
let ret = RenderBlogPostPage {
|
||||
global_settings,
|
||||
page_header: Some(page_header),
|
||||
title: original.page.title.clone(),
|
||||
self_link: Some(link_to_blog_post),
|
||||
children,
|
||||
footnotes,
|
||||
};
|
||||
Ok(ret)
|
||||
})
|
||||
}
|
||||
);
|
||||
|
@ -2,6 +2,7 @@ use serde::Serialize;
|
||||
|
||||
use super::macros::render;
|
||||
use super::render_context::RenderContext;
|
||||
use crate::context::macros::push_file;
|
||||
use crate::context::RenderDocumentElement;
|
||||
use crate::context::RenderRealFootnoteDefinition;
|
||||
use crate::error::CustomError;
|
||||
@ -164,32 +165,34 @@ render!(
|
||||
.get_index_page()
|
||||
.ok_or_else(|| format!("Blog post {} needs an index page.", original.original.id))?;
|
||||
|
||||
let title = index_page.title.clone();
|
||||
push_file!(render_context, &index_page.src, {
|
||||
let title = index_page.title.clone();
|
||||
|
||||
let children = index_page
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| RenderDocumentElement::new(render_context.clone(), child))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let children = index_page
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| RenderDocumentElement::new(render_context.clone(), child))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let footnotes = {
|
||||
let mut ret = Vec::new();
|
||||
let footnotes = {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for footnote in index_page.footnotes.iter() {
|
||||
ret.push(RenderRealFootnoteDefinition::new(
|
||||
render_context.clone(),
|
||||
footnote,
|
||||
)?);
|
||||
}
|
||||
for footnote in index_page.footnotes.iter() {
|
||||
ret.push(RenderRealFootnoteDefinition::new(
|
||||
render_context.clone(),
|
||||
footnote,
|
||||
)?);
|
||||
}
|
||||
|
||||
ret
|
||||
};
|
||||
ret
|
||||
};
|
||||
|
||||
Ok(RenderBlogStreamEntry {
|
||||
title,
|
||||
self_link: Some(link_to_blog_post),
|
||||
children,
|
||||
footnotes,
|
||||
Ok(RenderBlogStreamEntry {
|
||||
title,
|
||||
self_link: Some(link_to_blog_post),
|
||||
children,
|
||||
footnotes,
|
||||
})
|
||||
})
|
||||
}
|
||||
);
|
||||
|
45
src/context/dependency.rs
Normal file
45
src/context/dependency.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::CustomError;
|
||||
|
||||
use super::RenderContext;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Dependency {
|
||||
StaticFile { absolute_path: PathBuf },
|
||||
}
|
||||
|
||||
impl Dependency {
|
||||
pub(crate) async fn perform(
|
||||
&self,
|
||||
render_context: RenderContext<'_>,
|
||||
) -> Result<(), CustomError> {
|
||||
match self {
|
||||
Dependency::StaticFile { absolute_path } => {
|
||||
let input_root_directory = render_context.config.get_root_directory();
|
||||
let relative_path_to_file = absolute_path.strip_prefix(input_root_directory)?;
|
||||
let path_to_output = render_context
|
||||
.output_root_directory
|
||||
.join(relative_path_to_file);
|
||||
tokio::fs::create_dir_all(
|
||||
path_to_output
|
||||
.parent()
|
||||
.ok_or("Output file should have a containing directory.")?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if tokio::fs::metadata(&path_to_output).await.is_ok() {
|
||||
// TODO: compare hash and error out if they do not match.
|
||||
println!(
|
||||
"Not copying {} to {} because the output file already exists.",
|
||||
absolute_path.display(),
|
||||
path_to_output.display()
|
||||
);
|
||||
} else {
|
||||
tokio::fs::copy(absolute_path, path_to_output).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
src/context/dependency_manager.rs
Normal file
68
src/context/dependency_manager.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::CustomError;
|
||||
|
||||
use super::dependency::Dependency;
|
||||
|
||||
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>,
|
||||
|
||||
dependencies: Vec<Dependency>,
|
||||
}
|
||||
|
||||
impl DependencyManager {
|
||||
pub(crate) fn new() -> Self {
|
||||
DependencyManager {
|
||||
file_stack: Vec::new(),
|
||||
dependencies: 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(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_current_folder(&self) -> Result<&Path, CustomError> {
|
||||
Ok(self
|
||||
.file_stack
|
||||
.last()
|
||||
.ok_or("No current file")?
|
||||
.parent()
|
||||
.ok_or("Current file was not in a directory")?)
|
||||
}
|
||||
|
||||
pub(crate) fn mark_file_for_copying<P>(&mut self, path: P) -> Result<(), CustomError>
|
||||
where
|
||||
P: Into<PathBuf>,
|
||||
{
|
||||
self.dependencies.push(Dependency::StaticFile {
|
||||
absolute_path: path.into(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the dependencies and forget about them.
|
||||
pub(crate) fn take_dependencies(&mut self) -> Vec<Dependency> {
|
||||
let mut dependencies = Vec::new();
|
||||
std::mem::swap(&mut self.dependencies, &mut dependencies);
|
||||
dependencies
|
||||
}
|
||||
}
|
@ -35,3 +35,23 @@ macro_rules! 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;
|
||||
|
@ -11,6 +11,8 @@ mod clock;
|
||||
mod code;
|
||||
mod comment;
|
||||
mod comment_block;
|
||||
mod dependency;
|
||||
mod dependency_manager;
|
||||
mod diary_sexp;
|
||||
mod document_element;
|
||||
mod drawer;
|
||||
@ -72,6 +74,7 @@ pub(crate) use blog_post_page::RenderBlogPostPage;
|
||||
pub(crate) use blog_post_page::RenderBlogPostPageInput;
|
||||
pub(crate) use blog_stream::RenderBlogStream;
|
||||
pub(crate) use blog_stream::RenderBlogStreamInput;
|
||||
pub(crate) use dependency_manager::DependencyManager;
|
||||
pub(crate) use document_element::RenderDocumentElement;
|
||||
pub(crate) use element::RenderElement;
|
||||
pub(crate) use footnote_definition::RenderRealFootnoteDefinition;
|
||||
|
@ -4,6 +4,7 @@ use super::render_context::RenderContext;
|
||||
use super::GlobalSettings;
|
||||
use super::PageHeader;
|
||||
use super::RenderDocumentElement;
|
||||
use crate::context::macros::push_file;
|
||||
use crate::error::CustomError;
|
||||
use crate::intermediate::get_web_path;
|
||||
use crate::intermediate::IPage;
|
||||
@ -28,75 +29,77 @@ pub(crate) struct RenderPage {
|
||||
}
|
||||
|
||||
render!(RenderPage, IPage, original, render_context, {
|
||||
let css_files = vec![
|
||||
get_web_path(
|
||||
push_file!(render_context, &original.src, {
|
||||
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.output_root_directory,
|
||||
render_context.output_file,
|
||||
"stylesheet/reset.css",
|
||||
)?,
|
||||
get_web_path(
|
||||
"blog_post.js",
|
||||
)?];
|
||||
let global_settings = GlobalSettings::new(original.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,
|
||||
"stylesheet/main.css",
|
||||
)?,
|
||||
];
|
||||
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.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)?,
|
||||
)?;
|
||||
render_context
|
||||
.output_file
|
||||
.strip_prefix(render_context.output_root_directory)?,
|
||||
)?;
|
||||
|
||||
let children = {
|
||||
let mut children = Vec::new();
|
||||
let children = {
|
||||
let mut children = Vec::new();
|
||||
|
||||
for child in original.children.iter() {
|
||||
children.push(RenderDocumentElement::new(render_context.clone(), child)?);
|
||||
}
|
||||
for child in original.children.iter() {
|
||||
children.push(RenderDocumentElement::new(render_context.clone(), child)?);
|
||||
}
|
||||
|
||||
children
|
||||
};
|
||||
children
|
||||
};
|
||||
|
||||
let footnotes = {
|
||||
let mut ret = Vec::new();
|
||||
let footnotes = {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for footnote in original.footnotes.iter() {
|
||||
ret.push(RenderRealFootnoteDefinition::new(
|
||||
render_context.clone(),
|
||||
footnote,
|
||||
)?);
|
||||
}
|
||||
for footnote in original.footnotes.iter() {
|
||||
ret.push(RenderRealFootnoteDefinition::new(
|
||||
render_context.clone(),
|
||||
footnote,
|
||||
)?);
|
||||
}
|
||||
|
||||
ret
|
||||
};
|
||||
ret
|
||||
};
|
||||
|
||||
let ret = RenderPage {
|
||||
global_settings,
|
||||
page_header: Some(page_header),
|
||||
title: original.title.clone(),
|
||||
self_link: Some(link_to_blog_post),
|
||||
children,
|
||||
footnotes,
|
||||
};
|
||||
Ok(ret)
|
||||
let ret = RenderPage {
|
||||
global_settings,
|
||||
page_header: Some(page_header),
|
||||
title: original.title.clone(),
|
||||
self_link: Some(link_to_blog_post),
|
||||
children,
|
||||
footnotes,
|
||||
};
|
||||
Ok(ret)
|
||||
})
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ use serde::Serialize;
|
||||
use super::render_context::RenderContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::intermediate::IRegularLink;
|
||||
use crate::intermediate::LinkTarget;
|
||||
|
||||
use super::macros::render;
|
||||
use super::RenderObject;
|
||||
@ -10,13 +11,29 @@ use super::RenderObject;
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename = "regular_link")]
|
||||
pub(crate) struct RenderRegularLink {
|
||||
pub(crate) enum RenderRegularLink {
|
||||
#[serde(rename = "regular_link_anchor")]
|
||||
Anchor(RenderRegularLinkAnchor),
|
||||
#[serde(rename = "regular_link_image")]
|
||||
Image(RenderRegularLinkImage),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct RenderRegularLinkAnchor {
|
||||
target: String,
|
||||
raw_link: String,
|
||||
children: Vec<RenderObject>,
|
||||
post_blank: organic::types::PostBlank,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct RenderRegularLinkImage {
|
||||
src: String,
|
||||
alt: String,
|
||||
raw_link: String,
|
||||
post_blank: organic::types::PostBlank,
|
||||
}
|
||||
|
||||
render!(RenderRegularLink, IRegularLink, original, render_context, {
|
||||
let children = {
|
||||
let mut ret = Vec::new();
|
||||
@ -31,10 +48,22 @@ render!(RenderRegularLink, IRegularLink, original, render_context, {
|
||||
.generate_final_target(render_context.clone())?
|
||||
.unwrap_or_else(|| "".to_owned());
|
||||
|
||||
Ok(RenderRegularLink {
|
||||
target,
|
||||
raw_link: original.raw_link.clone(),
|
||||
children,
|
||||
post_blank: original.post_blank,
|
||||
})
|
||||
let render_link = match &original.target {
|
||||
LinkTarget::Raw(_) | LinkTarget::Post { .. } | LinkTarget::Target { .. } => {
|
||||
RenderRegularLink::Anchor(RenderRegularLinkAnchor {
|
||||
target,
|
||||
raw_link: original.raw_link.clone(),
|
||||
children,
|
||||
post_blank: original.post_blank,
|
||||
})
|
||||
}
|
||||
LinkTarget::Image { alt, .. } => RenderRegularLink::Image(RenderRegularLinkImage {
|
||||
src: target,
|
||||
alt: alt.clone(),
|
||||
raw_link: original.raw_link.clone(),
|
||||
post_blank: original.post_blank,
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(render_link)
|
||||
});
|
||||
|
@ -3,11 +3,12 @@ use std::path::Path;
|
||||
use crate::config::Config;
|
||||
use crate::error::CustomError;
|
||||
|
||||
use super::dependency_manager::RefDependencyManager;
|
||||
|
||||
/// The supporting information used for converting the intermediate representation into the dust context for rendering.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RenderContext<'intermediate> {
|
||||
pub(crate) config: &'intermediate Config,
|
||||
// TODO: Perhaps rename to output_root_directory.
|
||||
pub(crate) output_root_directory: &'intermediate Path,
|
||||
pub(crate) output_file: &'intermediate Path,
|
||||
|
||||
@ -17,6 +18,13 @@ pub(crate) struct RenderContext<'intermediate> {
|
||||
/// IDs, for example, multiple blog posts with footnotes in a blog
|
||||
/// stream.
|
||||
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> {
|
||||
@ -25,12 +33,14 @@ impl<'intermediate> RenderContext<'intermediate> {
|
||||
output_directory: &'intermediate Path,
|
||||
output_file: &'intermediate Path,
|
||||
id_addition: Option<&'intermediate str>,
|
||||
dependency_manager: RefDependencyManager,
|
||||
) -> Result<RenderContext<'intermediate>, CustomError> {
|
||||
Ok(RenderContext {
|
||||
config,
|
||||
output_root_directory: output_directory,
|
||||
output_file,
|
||||
id_addition,
|
||||
dependency_manager,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ impl BlogPost {
|
||||
) -> Result<BlogPost, CustomError> {
|
||||
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 mut ret = Vec::new();
|
||||
let org_files_iter = get_org_files(post_dir).await?;
|
||||
@ -43,6 +44,8 @@ impl BlogPost {
|
||||
}
|
||||
ret
|
||||
};
|
||||
|
||||
// Parse all the *.org files
|
||||
let parsed_org_files = {
|
||||
let mut ret = Vec::new();
|
||||
for (path, contents) in org_files.iter() {
|
||||
@ -73,7 +76,11 @@ impl BlogPost {
|
||||
ret.push(
|
||||
BlogPostPage::new(
|
||||
intermediate_context,
|
||||
BlogPostPageInput::new(relative_to_post_dir_path, parsed_document),
|
||||
BlogPostPageInput::new(
|
||||
relative_to_post_dir_path,
|
||||
real_path,
|
||||
parsed_document,
|
||||
),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
@ -11,17 +11,23 @@ use super::ISection;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BlogPostPageInput<'b, 'parse> {
|
||||
/// Relative path from the root of the blog post.
|
||||
path: PathBuf,
|
||||
|
||||
/// The path to the .org source for the file.
|
||||
src: PathBuf,
|
||||
document: &'b organic::types::Document<'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,
|
||||
src: S,
|
||||
document: &'b organic::types::Document<'parse>,
|
||||
) -> BlogPostPageInput<'b, 'parse> {
|
||||
BlogPostPageInput {
|
||||
path: path.into(),
|
||||
src: src.into(),
|
||||
document,
|
||||
}
|
||||
}
|
||||
@ -32,6 +38,9 @@ pub(crate) struct BlogPostPage {
|
||||
/// Relative path from the root of the blog post.
|
||||
pub(crate) path: PathBuf,
|
||||
|
||||
/// The path to the .org source for the file.
|
||||
pub(crate) src: PathBuf,
|
||||
|
||||
pub(crate) title: Option<String>,
|
||||
|
||||
pub(crate) date: Option<String>,
|
||||
@ -79,6 +88,7 @@ intermediate!(
|
||||
|
||||
Ok(BlogPostPage {
|
||||
path: original.path,
|
||||
src: original.src,
|
||||
title: get_title(original.document),
|
||||
date: get_date(original.document),
|
||||
children,
|
||||
|
@ -123,6 +123,7 @@ pub(crate) use radio_link::IRadioLink;
|
||||
pub(crate) use radio_target::IRadioTarget;
|
||||
pub(crate) use registry::Registry;
|
||||
pub(crate) use regular_link::IRegularLink;
|
||||
pub(crate) use regular_link::LinkTarget;
|
||||
pub(crate) use section::ISection;
|
||||
pub(crate) use special_block::ISpecialBlock;
|
||||
pub(crate) use src_block::ISrcBlock;
|
||||
|
@ -13,6 +13,9 @@ pub(crate) struct IPage {
|
||||
/// Relative path from the root of the pages directory.
|
||||
pub(crate) path: PathBuf,
|
||||
|
||||
/// The path to the .org source for the file.
|
||||
pub(crate) src: PathBuf,
|
||||
|
||||
pub(crate) title: Option<String>,
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -61,6 +64,7 @@ intermediate!(
|
||||
|
||||
Ok(IPage {
|
||||
path: original.path,
|
||||
src: original.src,
|
||||
title: get_title(original.document),
|
||||
date: get_date(original.document),
|
||||
children,
|
||||
@ -80,17 +84,23 @@ impl IPage {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PageInput<'b, 'parse> {
|
||||
/// Relative path from the root of the page.
|
||||
path: PathBuf,
|
||||
|
||||
/// The path to the .org source for the file.
|
||||
src: PathBuf,
|
||||
document: &'b organic::types::Document<'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,
|
||||
src: S,
|
||||
document: &'b organic::types::Document<'parse>,
|
||||
) -> PageInput<'b, 'parse> {
|
||||
PageInput {
|
||||
path: path.into(),
|
||||
src: src.into(),
|
||||
document,
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use organic::types::LinkType;
|
||||
use organic::types::StandardProperties;
|
||||
use url::Url;
|
||||
|
||||
@ -31,8 +35,11 @@ intermediate!(
|
||||
ret
|
||||
};
|
||||
let raw_link = original.get_raw_link();
|
||||
let target =
|
||||
LinkTarget::from_string(intermediate_context.clone(), raw_link.clone().into_owned())?;
|
||||
let target = LinkTarget::from_string(
|
||||
intermediate_context.clone(),
|
||||
raw_link.clone().into_owned(),
|
||||
&original.link_type,
|
||||
)?;
|
||||
Ok(IRegularLink {
|
||||
raw_link: raw_link.into_owned(),
|
||||
children,
|
||||
@ -42,7 +49,7 @@ intermediate!(
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum LinkTarget {
|
||||
Raw(String),
|
||||
Post {
|
||||
@ -52,13 +59,28 @@ pub(crate) enum LinkTarget {
|
||||
Target {
|
||||
target_id: String,
|
||||
},
|
||||
Image {
|
||||
src: String,
|
||||
alt: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl LinkTarget {
|
||||
pub(crate) fn from_string(
|
||||
intermediate_context: IntermediateContext<'_, '_>,
|
||||
input: String,
|
||||
link_type: &LinkType<'_>,
|
||||
) -> Result<LinkTarget, CustomError> {
|
||||
// If link type is file and the path ends in .svg then make it an image target
|
||||
if let LinkType::File = link_type
|
||||
&& input.to_ascii_lowercase().ends_with(".svg")
|
||||
{
|
||||
let src = Self::get_image_src(&input)?;
|
||||
let alt = Self::get_image_alt(&input)?;
|
||||
|
||||
return Ok(LinkTarget::Image { src, alt });
|
||||
};
|
||||
|
||||
let parsed = Url::parse(&input);
|
||||
if let Err(url::ParseError::RelativeUrlWithoutBase) = parsed {
|
||||
let target_id = {
|
||||
@ -121,6 +143,170 @@ impl LinkTarget {
|
||||
.unwrap_or_default(),
|
||||
target_id
|
||||
))),
|
||||
LinkTarget::Image { src, .. } => {
|
||||
let path_to_file = render_context
|
||||
.dependency_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_current_folder()?
|
||||
.join(src)
|
||||
.canonicalize()?;
|
||||
let input_root_directory = render_context.config.get_root_directory();
|
||||
let relative_path_to_file = path_to_file.strip_prefix(input_root_directory)?;
|
||||
let web_path = get_web_path(
|
||||
render_context.config,
|
||||
render_context.output_root_directory,
|
||||
render_context.output_file,
|
||||
relative_path_to_file,
|
||||
)?;
|
||||
let path_to_file = render_context
|
||||
.dependency_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mark_file_for_copying(path_to_file)?;
|
||||
Ok(Some(web_path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value for the src attribute of the image.
|
||||
fn get_image_src(input: &str) -> Result<String, CustomError> {
|
||||
let input = if input.to_ascii_lowercase().starts_with("file:") {
|
||||
Cow::Borrowed(&input[5..])
|
||||
} else {
|
||||
Cow::Borrowed(input)
|
||||
};
|
||||
let path = Path::new(input.as_ref());
|
||||
|
||||
if input.to_ascii_lowercase().starts_with("/ssh:") {
|
||||
return Ok(format!("file:/{}", input));
|
||||
}
|
||||
|
||||
if path.is_absolute() {
|
||||
return Ok(format!("file://{}", input));
|
||||
}
|
||||
return Ok(input.into_owned());
|
||||
}
|
||||
|
||||
/// Get file name from the last segment of an image path.
|
||||
fn get_image_alt(input: &str) -> Result<String, CustomError> {
|
||||
let input = if input.to_ascii_lowercase().starts_with("file:") {
|
||||
Cow::Borrowed(&input[5..])
|
||||
} else {
|
||||
Cow::Borrowed(input)
|
||||
};
|
||||
let path = Path::new(input.as_ref());
|
||||
match path
|
||||
.components()
|
||||
.last()
|
||||
.ok_or("Images should have at least one component in their path.")?
|
||||
{
|
||||
std::path::Component::Prefix(_) => {
|
||||
// Prefix components only occur on windows
|
||||
panic!("Prefix components are not supporterd.")
|
||||
}
|
||||
std::path::Component::RootDir
|
||||
| std::path::Component::CurDir
|
||||
| std::path::Component::ParentDir => {
|
||||
return Err(
|
||||
"Final component of an image path should be a normal component.".into(),
|
||||
);
|
||||
}
|
||||
std::path::Component::Normal(file_name) => Ok(file_name
|
||||
.to_str()
|
||||
.ok_or("Image link was not valid utf-8.")?
|
||||
.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::intermediate::Registry;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn link_target_raw() -> Result<(), CustomError> {
|
||||
let registry = Registry::new();
|
||||
let registry = Arc::new(Mutex::new(registry));
|
||||
let intermediate_context = IntermediateContext::new(registry)?;
|
||||
for (inp, typ) in [(
|
||||
"https://test.example/foo",
|
||||
LinkType::Protocol(Cow::from("https")),
|
||||
)] {
|
||||
assert_eq!(
|
||||
LinkTarget::from_string(intermediate_context.clone(), inp.to_owned(), &typ)?,
|
||||
LinkTarget::Raw(inp.to_owned())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_target_image() -> Result<(), CustomError> {
|
||||
let registry = Registry::new();
|
||||
let registry = Arc::new(Mutex::new(registry));
|
||||
let intermediate_context = IntermediateContext::new(registry)?;
|
||||
for (inp, typ, expected_src, expected_alt) in [
|
||||
("file:image.svg", LinkType::File, "image.svg", "image.svg"),
|
||||
(
|
||||
"file:/image.svg",
|
||||
LinkType::File,
|
||||
"file:///image.svg",
|
||||
"image.svg",
|
||||
),
|
||||
(
|
||||
"file:./image.svg",
|
||||
LinkType::File,
|
||||
"./image.svg",
|
||||
"image.svg",
|
||||
),
|
||||
(
|
||||
"/image.svg",
|
||||
LinkType::File,
|
||||
"file:///image.svg",
|
||||
"image.svg",
|
||||
),
|
||||
("./image.svg", LinkType::File, "./image.svg", "image.svg"),
|
||||
("./image.SVG", LinkType::File, "./image.SVG", "image.SVG"),
|
||||
(
|
||||
"./image and stuff.SVG",
|
||||
LinkType::File,
|
||||
"./image and stuff.SVG",
|
||||
"image and stuff.SVG",
|
||||
),
|
||||
(
|
||||
"/ssh:admin@test.example:important/file.svg",
|
||||
LinkType::File,
|
||||
"file://ssh:admin@test.example:important/file.svg",
|
||||
"file.svg",
|
||||
),
|
||||
(
|
||||
"file:/ssh:admin@test.example:important/file.svg",
|
||||
LinkType::File,
|
||||
"file://ssh:admin@test.example:important/file.svg",
|
||||
"file.svg",
|
||||
),
|
||||
(
|
||||
"file:/foo/bar/baz/image.svg",
|
||||
LinkType::File,
|
||||
"file:///foo/bar/baz/image.svg",
|
||||
"image.svg",
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
LinkTarget::from_string(intermediate_context.clone(), inp.to_owned(), &typ)?,
|
||||
LinkTarget::Image {
|
||||
src: expected_src.to_owned(),
|
||||
alt: expected_alt.to_owned()
|
||||
}
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![feature(let_chains)]
|
||||
#![feature(async_closure)]
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::Parser;
|
||||
|
Loading…
x
Reference in New Issue
Block a user