Compare commits

...

5 Commits

Author SHA1 Message Date
Tom Alexander
e3b5f7f74f
Rename blog_post module to intermediate.
This module is mostly the intermediate representation of the AST, so the renaming is to make that more clear. The three forms are parsed => intermediate => render.

Parsed comes from Organic and is a direct translation of the org-mode text.

Intermediate converts the parsed data into owned values and does any calculations that are needed on the data (for example: assigning numbers to footnotes.)

Render takes intermediate and translates it into the format expected by the dust templates. The processing in this step should be minimal since all the logic should be in the intermediate step.
2023-10-27 13:10:21 -04:00
Tom Alexander
1ac39c2a6f
Add RenderPlainText. 2023-10-27 13:01:45 -04:00
Tom Alexander
744d3e50fb
Convert intermediate objects into render objects. 2023-10-27 12:47:12 -04:00
Tom Alexander
4c59011389
Copy heading level. 2023-10-27 12:14:07 -04:00
Tom Alexander
ba2756c762
Create intermediate representation for plain text. 2023-10-27 10:23:05 -04:00
23 changed files with 281 additions and 53 deletions

View File

@ -1,8 +0,0 @@
use super::Heading;
use super::Section;
#[derive(Debug)]
pub(crate) enum DocumentElement {
Heading(Heading),
Section(Section),
}

View File

@ -1,2 +0,0 @@
#[derive(Debug)]
pub(crate) enum Element {}

View File

@ -1,14 +0,0 @@
use crate::error::CustomError;
use super::Object;
#[derive(Debug)]
pub(crate) struct Heading {
title: Vec<Object>,
}
impl Heading {
pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<Heading, CustomError> {
Ok(Heading { title: Vec::new() })
}
}

View File

@ -1,2 +0,0 @@
#[derive(Debug)]
pub(crate) enum Object {}

View File

@ -4,10 +4,10 @@ use std::path::PathBuf;
use include_dir::include_dir; use include_dir::include_dir;
use include_dir::Dir; use include_dir::Dir;
use crate::blog_post::convert_blog_post_page_to_render_context;
use crate::blog_post::BlogPost;
use crate::config::Config; use crate::config::Config;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::convert_blog_post_page_to_render_context;
use crate::intermediate::BlogPost;
use crate::render::DusterRenderer; use crate::render::DusterRenderer;
use crate::render::RendererIntegration; use crate::render::RendererIntegration;

View File

@ -1,10 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::blog_post::BlogPost;
use crate::cli::parameters::BuildArgs; use crate::cli::parameters::BuildArgs;
use crate::command::build::render::SiteRenderer; use crate::command::build::render::SiteRenderer;
use crate::config::Config; use crate::config::Config;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::BlogPost;
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?;

View File

@ -1,10 +1,36 @@
use std::path::Path;
use serde::Serialize; use serde::Serialize;
use crate::config::Config;
use crate::error::CustomError;
use crate::intermediate::IHeading;
use super::RenderObject; use super::RenderObject;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
#[serde(rename = "heading")] #[serde(rename = "heading")]
pub(crate) struct RenderHeading { pub(crate) struct RenderHeading {
level: organic::types::HeadlineLevel,
title: Vec<RenderObject>, title: Vec<RenderObject>,
} }
impl RenderHeading {
pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>(
config: &Config,
output_directory: D,
output_file: F,
heading: &IHeading,
) -> Result<RenderHeading, CustomError> {
let title = heading
.title
.iter()
.map(|obj| RenderObject::new(config, &output_directory, &output_file, obj))
.collect::<Result<Vec<_>, _>>()?;
Ok(RenderHeading {
level: heading.level,
title,
})
}
}

View File

@ -4,6 +4,7 @@ mod element;
mod global_settings; mod global_settings;
mod heading; mod heading;
mod object; mod object;
mod plain_text;
mod section; mod section;
pub(crate) use blog_post_page::RenderBlogPostPage; pub(crate) use blog_post_page::RenderBlogPostPage;

View File

@ -1,5 +1,33 @@
use std::path::Path;
use serde::Serialize; use serde::Serialize;
use crate::config::Config;
use crate::error::CustomError;
use crate::intermediate::IObject;
use super::plain_text::RenderPlainText;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(untagged)] #[serde(untagged)]
pub(crate) enum RenderObject {} pub(crate) enum RenderObject {
PlainText(RenderPlainText),
}
impl RenderObject {
pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>(
config: &Config,
output_directory: D,
output_file: F,
object: &IObject,
) -> Result<RenderObject, CustomError> {
match object {
IObject::PlainText(inner) => Ok(RenderObject::PlainText(RenderPlainText::new(
config,
output_directory,
output_file,
inner,
)?)),
}
}
}

23
src/context/plain_text.rs Normal file
View File

@ -0,0 +1,23 @@
use std::path::Path;
use serde::Serialize;
use crate::config::Config;
use crate::error::CustomError;
use crate::intermediate::IPlainText;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "heading")]
pub(crate) struct RenderPlainText {}
impl RenderPlainText {
pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>(
config: &Config,
output_directory: D,
output_file: F,
heading: &IPlainText,
) -> Result<RenderPlainText, CustomError> {
Ok(RenderPlainText {})
}
}

View File

@ -2,9 +2,9 @@ use std::path::Path;
use serde::Serialize; use serde::Serialize;
use crate::blog_post::Section;
use crate::config::Config; use crate::config::Config;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::ISection;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -16,7 +16,7 @@ impl RenderSection {
config: &Config, config: &Config,
output_directory: D, output_directory: D,
output_file: F, output_file: F,
section: &Section, section: &ISection,
) -> Result<RenderSection, CustomError> { ) -> Result<RenderSection, CustomError> {
Ok(RenderSection {}) Ok(RenderSection {})
} }

View File

@ -5,10 +5,14 @@ use std::path::PathBuf;
use crate::config::Config; use crate::config::Config;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::RenderBlogPostPage; use crate::context::RenderBlogPostPage;
use crate::context::RenderDocumentElement;
use crate::context::RenderHeading;
use crate::context::RenderSection;
use crate::error::CustomError; use crate::error::CustomError;
use super::BlogPost; use super::BlogPost;
use super::BlogPostPage; use super::BlogPostPage;
use super::IDocumentElement;
pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef<Path>>( pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef<Path>>(
config: &Config, config: &Config,
@ -39,11 +43,38 @@ pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef<
output_file.strip_prefix(output_directory)?, output_file.strip_prefix(output_directory)?,
)?; )?;
let children = {
let mut children = Vec::new();
for child in page.children.iter() {
match child {
IDocumentElement::Heading(heading) => {
children.push(RenderDocumentElement::Heading(RenderHeading::new(
config,
output_directory,
output_file,
heading,
)?));
}
IDocumentElement::Section(section) => {
children.push(RenderDocumentElement::Section(RenderSection::new(
config,
output_directory,
output_file,
section,
)?));
}
}
}
children
};
let ret = RenderBlogPostPage::new( let ret = RenderBlogPostPage::new(
global_settings, global_settings,
page.title.clone(), page.title.clone(),
Some(link_to_blog_post), Some(link_to_blog_post),
Vec::new(), children,
); );
Ok(ret) Ok(ret)
} }

View File

@ -7,13 +7,13 @@ use walkdir::WalkDir;
use crate::error::CustomError; use crate::error::CustomError;
use super::BlogPostPage; use super::BlogPostPage;
use super::DocumentElement; use super::IDocumentElement;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct BlogPost { pub(crate) struct BlogPost {
pub(crate) id: String, pub(crate) id: String,
pub(crate) pages: Vec<BlogPostPage>, pub(crate) pages: Vec<BlogPostPage>,
pub(crate) children: Vec<DocumentElement>, pub(crate) children: Vec<IDocumentElement>,
} }
impl BlogPost { impl BlogPost {

View File

@ -0,0 +1,8 @@
use super::IHeading;
use super::ISection;
#[derive(Debug)]
pub(crate) enum IDocumentElement {
Heading(IHeading),
Section(ISection),
}

View File

@ -0,0 +1,2 @@
#[derive(Debug)]
pub(crate) enum IElement {}

View File

@ -0,0 +1,23 @@
use crate::error::CustomError;
use super::IObject;
#[derive(Debug)]
pub(crate) struct IHeading {
pub(crate) level: organic::types::HeadlineLevel,
pub(crate) title: Vec<IObject>,
}
impl IHeading {
pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<IHeading, CustomError> {
let title = heading
.title
.iter()
.map(IObject::new)
.collect::<Result<Vec<_>, _>>()?;
Ok(IHeading {
title,
level: heading.level,
})
}
}

View File

@ -5,12 +5,15 @@ mod element;
mod heading; mod heading;
mod object; mod object;
mod page; mod page;
mod plain_text;
mod section; mod section;
mod util;
pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use convert::convert_blog_post_page_to_render_context;
pub(crate) use definition::BlogPost; pub(crate) use definition::BlogPost;
pub(crate) use document_element::DocumentElement; pub(crate) use document_element::IDocumentElement;
pub(crate) use element::Element; pub(crate) use element::IElement;
pub(crate) use heading::Heading; pub(crate) use heading::IHeading;
pub(crate) use object::Object; pub(crate) use object::IObject;
pub(crate) use page::BlogPostPage; pub(crate) use page::BlogPostPage;
pub(crate) use section::Section; pub(crate) use plain_text::IPlainText;
pub(crate) use section::ISection;

View File

@ -0,0 +1,44 @@
use crate::error::CustomError;
use super::plain_text::IPlainText;
#[derive(Debug)]
pub(crate) enum IObject {
PlainText(IPlainText),
}
impl IObject {
pub(crate) fn new(obj: &organic::types::Object<'_>) -> Result<IObject, CustomError> {
match obj {
organic::types::Object::Bold(_) => todo!(),
organic::types::Object::Italic(_) => todo!(),
organic::types::Object::Underline(_) => todo!(),
organic::types::Object::StrikeThrough(_) => todo!(),
organic::types::Object::Code(_) => todo!(),
organic::types::Object::Verbatim(_) => todo!(),
organic::types::Object::PlainText(plain_text) => {
Ok(IObject::PlainText(IPlainText::new(plain_text)?))
}
organic::types::Object::RegularLink(_) => todo!(),
organic::types::Object::RadioLink(_) => todo!(),
organic::types::Object::RadioTarget(_) => todo!(),
organic::types::Object::PlainLink(_) => todo!(),
organic::types::Object::AngleLink(_) => todo!(),
organic::types::Object::OrgMacro(_) => todo!(),
organic::types::Object::Entity(_) => todo!(),
organic::types::Object::LatexFragment(_) => todo!(),
organic::types::Object::ExportSnippet(_) => todo!(),
organic::types::Object::FootnoteReference(_) => todo!(),
organic::types::Object::Citation(_) => todo!(),
organic::types::Object::CitationReference(_) => todo!(),
organic::types::Object::InlineBabelCall(_) => todo!(),
organic::types::Object::InlineSourceBlock(_) => todo!(),
organic::types::Object::LineBreak(_) => todo!(),
organic::types::Object::Target(_) => todo!(),
organic::types::Object::StatisticsCookie(_) => todo!(),
organic::types::Object::Subscript(_) => todo!(),
organic::types::Object::Superscript(_) => todo!(),
organic::types::Object::Timestamp(_) => todo!(),
}
}
}

View File

@ -2,9 +2,9 @@ use std::path::PathBuf;
use crate::error::CustomError; use crate::error::CustomError;
use super::DocumentElement; use super::IDocumentElement;
use super::Heading; use super::IHeading;
use super::Section; use super::ISection;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct BlogPostPage { pub(crate) struct BlogPostPage {
@ -13,7 +13,7 @@ pub(crate) struct BlogPostPage {
pub(crate) title: Option<String>, pub(crate) title: Option<String>,
pub(crate) children: Vec<DocumentElement>, pub(crate) children: Vec<IDocumentElement>,
} }
impl BlogPostPage { impl BlogPostPage {
@ -24,10 +24,10 @@ impl BlogPostPage {
let path = path.into(); let path = path.into();
let mut children = Vec::new(); let mut children = Vec::new();
if let Some(section) = document.zeroth_section.as_ref() { if let Some(section) = document.zeroth_section.as_ref() {
children.push(DocumentElement::Section(Section::new(section)?)); children.push(IDocumentElement::Section(ISection::new(section)?));
} }
for heading in document.children.iter() { for heading in document.children.iter() {
children.push(DocumentElement::Heading(Heading::new(heading)?)); children.push(IDocumentElement::Heading(IHeading::new(heading)?));
} }
Ok(BlogPostPage { Ok(BlogPostPage {

View File

@ -0,0 +1,17 @@
use crate::error::CustomError;
use crate::intermediate::util::coalesce_whitespace;
#[derive(Debug)]
pub(crate) struct IPlainText {
source: String,
}
impl IPlainText {
pub(crate) fn new(
plain_text: &organic::types::PlainText<'_>,
) -> Result<IPlainText, CustomError> {
Ok(IPlainText {
source: coalesce_whitespace(plain_text.source).into_owned(),
})
}
}

View File

@ -1,10 +1,10 @@
use crate::error::CustomError; use crate::error::CustomError;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Section {} pub(crate) struct ISection {}
impl Section { impl ISection {
pub(crate) fn new(section: &organic::types::Section<'_>) -> Result<Section, CustomError> { pub(crate) fn new(section: &organic::types::Section<'_>) -> Result<ISection, CustomError> {
Ok(Section {}) Ok(ISection {})
} }
} }

48
src/intermediate/util.rs Normal file
View File

@ -0,0 +1,48 @@
use std::borrow::Cow;
/// Removes all whitespace from a string.
///
/// Example: "foo bar" => "foobar" and "foo \n bar" => "foobar".
#[allow(dead_code)]
pub(crate) fn coalesce_whitespace(input: &str) -> Cow<'_, str> {
let mut state = CoalesceWhitespace::Normal;
for (offset, c) in input.char_indices() {
match (&mut state, c) {
(CoalesceWhitespace::Normal, ' ' | '\t' | '\r' | '\n') => {
let mut ret = String::with_capacity(input.len());
ret.push_str(&input[..offset]);
ret.push(' ');
state = CoalesceWhitespace::HasWhitespace {
in_whitespace: true,
ret,
};
}
(CoalesceWhitespace::Normal, _) => {}
(
CoalesceWhitespace::HasWhitespace { in_whitespace, ret },
' ' | '\t' | '\r' | '\n',
) => {
if !*in_whitespace {
*in_whitespace = true;
ret.push(' ');
}
}
(CoalesceWhitespace::HasWhitespace { in_whitespace, ret }, _) => {
*in_whitespace = false;
ret.push(c);
}
}
}
match state {
CoalesceWhitespace::Normal => Cow::Borrowed(input),
CoalesceWhitespace::HasWhitespace {
in_whitespace: _,
ret,
} => Cow::Owned(ret),
}
}
enum CoalesceWhitespace {
Normal,
HasWhitespace { in_whitespace: bool, ret: String },
}

View File

@ -7,13 +7,13 @@ use self::cli::parameters::Commands;
use self::command::build::build_site; use self::command::build::build_site;
use self::command::init::init_writer_folder; use self::command::init::init_writer_folder;
use self::error::CustomError; use self::error::CustomError;
mod blog_post;
mod cli; mod cli;
mod command; mod command;
mod config; mod config;
mod error;
mod render;
mod context; mod context;
mod error;
mod intermediate;
mod render;
fn main() -> Result<ExitCode, CustomError> { fn main() -> Result<ExitCode, CustomError> {
let rt = tokio::runtime::Runtime::new()?; let rt = tokio::runtime::Runtime::new()?;