From acaa12cb6e34eea2e7ed7956c8802564b32933ad Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Fri, 20 Oct 2023 20:16:22 -0400 Subject: [PATCH 01/47] Read the org files inside the writer directory. --- Cargo.lock | 1 + Cargo.toml | 1 + src/command/build/runner.rs | 37 ++++++++++++++++++++++++++++++++++++- src/config/full.rs | 7 +++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6983727..b1774c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,4 +514,5 @@ dependencies = [ "serde", "tokio", "toml", + "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index 44b8f2f..88be60f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ serde = { version = "1.0.189", default-features = false, features = ["std", "der 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" diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index c9183a3..46c7a5f 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -1,7 +1,42 @@ +use std::path::Path; +use std::path::PathBuf; + use crate::cli::parameters::BuildArgs; use crate::config::Config; +use tokio::task::JoinHandle; +use walkdir::WalkDir; pub(crate) async fn build_site(args: BuildArgs) -> Result<(), Box> { - let _config = Config::load_from_file(args.config).await?; + let config = Config::load_from_file(args.config).await?; + let org_files = get_org_files(config.get_root_directory())?; + Ok(()) } + +async fn read_file(path: PathBuf) -> std::io::Result<(PathBuf, String)> { + let contents = tokio::fs::read_to_string(&path).await?; + Ok((path, contents)) +} + +fn get_org_files>( + root_dir: P, +) -> Result>>, walkdir::Error> { + let org_files = WalkDir::new(root_dir) + .into_iter() + .filter(|e| match e { + Ok(dir_entry) => { + dir_entry.file_type().is_file() + && Path::new(dir_entry.file_name()) + .extension() + .map(|ext| ext.to_ascii_lowercase() == "org") + .unwrap_or(false) + } + Err(_) => true, + }) + .collect::, _>>()?; + let org_files = org_files + .into_iter() + .map(walkdir::DirEntry::into_path) + .map(|path| tokio::spawn(read_file(path))); + Ok(org_files) +} diff --git a/src/config/full.rs b/src/config/full.rs index 49a0e8f..73837f2 100644 --- a/src/config/full.rs +++ b/src/config/full.rs @@ -44,4 +44,11 @@ impl Config { .await?; Ok(()) } + + pub(crate) fn get_root_directory(&self) -> &Path { + &self + .config_path + .parent() + .expect("Config file must exist inside a directory.") + } } From 816780589fcd7cfb8e2e8fa4f0e4a9113eb9651d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 21 Oct 2023 18:00:51 -0400 Subject: [PATCH 02/47] Parse the org-mode files. --- src/command/build/runner.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index 46c7a5f..e241370 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -8,7 +8,22 @@ use walkdir::WalkDir; pub(crate) async fn build_site(args: BuildArgs) -> Result<(), Box> { let config = Config::load_from_file(args.config).await?; - let org_files = get_org_files(config.get_root_directory())?; + let org_files = { + let mut ret = Vec::new(); + let org_files_iter = get_org_files(config.get_root_directory())?; + for entry in org_files_iter { + ret.push(entry.await??); + } + ret + }; + let parsed_org_files = { + let mut ret = Vec::new(); + for (path, contents) in org_files.iter() { + let parsed = organic::parser::parse_file(contents.as_str(), Some(path))?; + ret.push((path, contents, parsed)); + } + ret + }; Ok(()) } From d8fc49797ef1279c564165866bc51c1312fa7feb Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 12:04:09 -0400 Subject: [PATCH 03/47] Moving into a load_blog_post function to create a BlogPost struct. --- src/blog_post/definition.rs | 61 +++++++++++++++++++++++++++++++++++++ src/blog_post/mod.rs | 0 src/main.rs | 1 + 3 files changed, 62 insertions(+) create mode 100644 src/blog_post/definition.rs create mode 100644 src/blog_post/mod.rs diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs new file mode 100644 index 0000000..4c7da73 --- /dev/null +++ b/src/blog_post/definition.rs @@ -0,0 +1,61 @@ +#[derive(Debug)] +struct BlogPost { + id: String, +} + +impl BlogPost { + pub(crate) async fn load_blog_post, R: AsRef>( + root_dir: R, + post_dir: P, + ) -> Result> { + async fn inner( + root_dir: &Path, + post_dir: &Path, + ) -> Result> { + let org_files = { + let mut ret = Vec::new(); + let org_files_iter = get_org_files(post_dir)?; + for entry in org_files_iter { + ret.push(entry.await??); + } + ret + }; + let parsed_org_files = { + let mut ret = Vec::new(); + for (path, contents) in org_files.iter() { + let parsed = organic::parser::parse_file(contents.as_str(), Some(path))?; + ret.push((path, contents, parsed)); + } + ret + }; + + Ok(BlogPost { + id: "foo".to_owned(), + }) + } + inner(root_dir.as_ref(), post_dir.as_ref()).await + } +} + +fn get_org_files>( + root_dir: P, +) -> Result>>, walkdir::Error> { + let org_files = WalkDir::new(root_dir) + .into_iter() + .filter(|e| match e { + Ok(dir_entry) => { + dir_entry.file_type().is_file() + && Path::new(dir_entry.file_name()) + .extension() + .map(|ext| ext.to_ascii_lowercase() == "org") + .unwrap_or(false) + } + Err(_) => true, + }) + .collect::, _>>()?; + let org_files = org_files + .into_iter() + .map(walkdir::DirEntry::into_path) + .map(|path| tokio::spawn(read_file(path))); + Ok(org_files) +} diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs index 76d095c..b16e9cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use self::cli::parameters::Cli; use self::cli::parameters::Commands; use self::command::build::build_site; use self::command::init::init_writer_folder; +mod blog_post; mod cli; mod command; mod config; From 2f0f3ab34645af9294a8b3ad2508d9d585a2661b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 13:44:03 -0400 Subject: [PATCH 04/47] Switch to using CustomError because a boxed StdError is not Send. --- src/blog_post/definition.rs | 25 ++++++++++++----- src/blog_post/mod.rs | 2 ++ src/command/build/runner.rs | 54 +++++++------------------------------ src/command/init/runner.rs | 3 ++- src/config/full.rs | 18 ++++++++----- src/error/error.rs | 45 +++++++++++++++++++++++++++++++ src/error/mod.rs | 3 +++ src/main.rs | 6 +++-- 8 files changed, 95 insertions(+), 61 deletions(-) create mode 100644 src/error/error.rs create mode 100644 src/error/mod.rs diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs index 4c7da73..5ef06b0 100644 --- a/src/blog_post/definition.rs +++ b/src/blog_post/definition.rs @@ -1,5 +1,13 @@ +use std::path::Path; +use std::path::PathBuf; + +use tokio::task::JoinHandle; +use walkdir::WalkDir; + +use crate::error::CustomError; + #[derive(Debug)] -struct BlogPost { +pub(crate) struct BlogPost { id: String, } @@ -7,11 +15,8 @@ impl BlogPost { pub(crate) async fn load_blog_post, R: AsRef>( root_dir: R, post_dir: P, - ) -> Result> { - async fn inner( - root_dir: &Path, - post_dir: &Path, - ) -> Result> { + ) -> Result { + async fn inner(root_dir: &Path, post_dir: &Path) -> Result { let org_files = { let mut ret = Vec::new(); let org_files_iter = get_org_files(post_dir)?; @@ -23,7 +28,8 @@ impl BlogPost { let parsed_org_files = { let mut ret = Vec::new(); for (path, contents) in org_files.iter() { - let parsed = organic::parser::parse_file(contents.as_str(), Some(path))?; + let parsed = organic::parser::parse_file(contents.as_str(), Some(path)) + .map_err(|_| CustomError::Static("Failed to parse org-mode document."))?; ret.push((path, contents, parsed)); } ret @@ -37,6 +43,11 @@ impl BlogPost { } } +async fn read_file(path: PathBuf) -> std::io::Result<(PathBuf, String)> { + let contents = tokio::fs::read_to_string(&path).await?; + Ok((path, contents)) +} + fn get_org_files>( root_dir: P, ) -> Result>>, walkdir::Error> { diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index e69de29..9d44a74 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -0,0 +1,2 @@ +mod definition; +pub(crate) use definition::BlogPost; diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index e241370..38fb73e 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -1,57 +1,23 @@ -use std::path::Path; -use std::path::PathBuf; - +use crate::blog_post::BlogPost; use crate::cli::parameters::BuildArgs; use crate::config::Config; -use tokio::task::JoinHandle; +use crate::error::CustomError; use walkdir::WalkDir; -pub(crate) async fn build_site(args: BuildArgs) -> Result<(), Box> { +pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> { let config = Config::load_from_file(args.config).await?; - let org_files = { - let mut ret = Vec::new(); - let org_files_iter = get_org_files(config.get_root_directory())?; - for entry in org_files_iter { - ret.push(entry.await??); - } - ret - }; - let parsed_org_files = { - let mut ret = Vec::new(); - for (path, contents) in org_files.iter() { - let parsed = organic::parser::parse_file(contents.as_str(), Some(path))?; - ret.push((path, contents, parsed)); - } - ret - }; - - Ok(()) -} - -async fn read_file(path: PathBuf) -> std::io::Result<(PathBuf, String)> { - let contents = tokio::fs::read_to_string(&path).await?; - Ok((path, contents)) -} - -fn get_org_files>( - root_dir: P, -) -> Result>>, walkdir::Error> { - let org_files = WalkDir::new(root_dir) + let root_directory = config.get_root_directory().to_owned(); + let post_directories = WalkDir::new(config.get_posts_directory()) .into_iter() .filter(|e| match e { - Ok(dir_entry) => { - dir_entry.file_type().is_file() - && Path::new(dir_entry.file_name()) - .extension() - .map(|ext| ext.to_ascii_lowercase() == "org") - .unwrap_or(false) - } + Ok(entry) if entry.depth() == 1 && entry.file_type().is_dir() => true, + Ok(_) => false, Err(_) => true, }) .collect::, _>>()?; - let org_files = org_files + let load_jobs = post_directories .into_iter() .map(walkdir::DirEntry::into_path) - .map(|path| tokio::spawn(read_file(path))); - Ok(org_files) + .map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path))); + Ok(()) } diff --git a/src/command/init/runner.rs b/src/command/init/runner.rs index 976f7a2..40d1871 100644 --- a/src/command/init/runner.rs +++ b/src/command/init/runner.rs @@ -1,7 +1,8 @@ use crate::cli::parameters::InitArgs; use crate::config::Config; +use crate::error::CustomError; -pub(crate) async fn init_writer_folder(args: InitArgs) -> Result<(), Box> { +pub(crate) async fn init_writer_folder(args: InitArgs) -> Result<(), CustomError> { if args.path.exists() && !args.path.is_dir() { return Err("The supplied path exists but is not a directory. Aborting.".into()); } diff --git a/src/config/full.rs b/src/config/full.rs index 73837f2..c2aeb61 100644 --- a/src/config/full.rs +++ b/src/config/full.rs @@ -3,6 +3,8 @@ use std::path::PathBuf; use tokio::fs::File; use tokio::io::AsyncWriteExt; +use crate::error::CustomError; + use super::raw::RawConfig; /// This is the config struct used by most of the code, which is an interpreted version of the RawConfig struct which is the raw disk-representation of the config. @@ -12,8 +14,8 @@ pub(crate) struct Config { } impl Config { - pub(crate) fn new>(root_dir: P) -> Result> { - fn inner(root_dir: &Path) -> Result> { + pub(crate) fn new>(root_dir: P) -> Result { + fn inner(root_dir: &Path) -> Result { let file_path = root_dir.join("writer.toml"); Ok(Config { raw: RawConfig::default(), @@ -23,10 +25,8 @@ impl Config { inner(root_dir.as_ref()) } - pub(crate) async fn load_from_file>( - path: P, - ) -> Result> { - async fn inner(path: PathBuf) -> Result> { + pub(crate) async fn load_from_file>(path: P) -> Result { + async fn inner(path: PathBuf) -> Result { let contents = tokio::fs::read_to_string(&path).await?; let parsed_contents: RawConfig = toml::from_str(contents.as_str())?; Ok(Config { @@ -37,7 +37,7 @@ impl Config { inner(path.into()).await } - pub(crate) async fn write_to_disk(&self) -> Result<(), Box> { + pub(crate) async fn write_to_disk(&self) -> Result<(), CustomError> { let mut config_file = File::create(&self.config_path).await?; config_file .write_all(toml::to_string(&self.raw)?.as_bytes()) @@ -51,4 +51,8 @@ impl Config { .parent() .expect("Config file must exist inside a directory.") } + + pub(crate) fn get_posts_directory(&self) -> PathBuf { + self.get_root_directory().join("posts") + } } diff --git a/src/error/error.rs b/src/error/error.rs new file mode 100644 index 0000000..130f283 --- /dev/null +++ b/src/error/error.rs @@ -0,0 +1,45 @@ +#[derive(Debug)] +pub(crate) enum CustomError { + Static(&'static str), + IO(std::io::Error), + TomlSerialize(toml::ser::Error), + TomlDeserialize(toml::de::Error), + WalkDir(walkdir::Error), + Tokio(tokio::task::JoinError), +} + +impl From for CustomError { + fn from(value: std::io::Error) -> Self { + CustomError::IO(value) + } +} + +impl From<&'static str> for CustomError { + fn from(value: &'static str) -> Self { + CustomError::Static(value) + } +} + +impl From for CustomError { + fn from(value: toml::ser::Error) -> Self { + CustomError::TomlSerialize(value) + } +} + +impl From for CustomError { + fn from(value: toml::de::Error) -> Self { + CustomError::TomlDeserialize(value) + } +} + +impl From for CustomError { + fn from(value: walkdir::Error) -> Self { + CustomError::WalkDir(value) + } +} + +impl From for CustomError { + fn from(value: tokio::task::JoinError) -> Self { + CustomError::Tokio(value) + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..84e7c7c --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,3 @@ +#[allow(clippy::module_inception)] +mod error; +pub(crate) use error::CustomError; diff --git a/src/main.rs b/src/main.rs index b16e9cb..124c99b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,14 @@ use self::cli::parameters::Cli; use self::cli::parameters::Commands; use self::command::build::build_site; use self::command::init::init_writer_folder; +use self::error::CustomError; mod blog_post; mod cli; mod command; mod config; +mod error; -fn main() -> Result> { +fn main() -> Result { let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { let main_body_result = main_body().await; @@ -19,7 +21,7 @@ fn main() -> Result> { }) } -async fn main_body() -> Result> { +async fn main_body() -> Result { let args = Cli::parse(); match args.command { Commands::Init(args) => { From 07e4209048a8d8a62c755fb298e21a481aedf625 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 13:50:11 -0400 Subject: [PATCH 05/47] Setting the post id based on the folder name. --- src/blog_post/definition.rs | 6 +++++- src/command/build/runner.rs | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs index 5ef06b0..5179858 100644 --- a/src/blog_post/definition.rs +++ b/src/blog_post/definition.rs @@ -17,6 +17,10 @@ impl BlogPost { post_dir: P, ) -> Result { async fn inner(root_dir: &Path, post_dir: &Path) -> Result { + let post_id = post_dir + .file_name() + .expect("The post directory should have a name."); + let org_files = { let mut ret = Vec::new(); let org_files_iter = get_org_files(post_dir)?; @@ -36,7 +40,7 @@ impl BlogPost { }; Ok(BlogPost { - id: "foo".to_owned(), + id: post_id.to_string_lossy().into_owned(), }) } inner(root_dir.as_ref(), post_dir.as_ref()).await diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index 38fb73e..9ca5f4a 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -19,5 +19,10 @@ pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> { .into_iter() .map(walkdir::DirEntry::into_path) .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(()) } From a9fbb4cd63dea36a6bbddc1d2e665286ccda88ed Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 14:40:59 -0400 Subject: [PATCH 06/47] Get the output directory and clear it. --- src/command/build/runner.rs | 22 ++++++++++++++++++++++ src/config/full.rs | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index 9ca5f4a..b6ed39a 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use crate::blog_post::BlogPost; use crate::cli::parameters::BuildArgs; use crate::config::Config; @@ -7,6 +9,7 @@ use walkdir::WalkDir; 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 = WalkDir::new(config.get_posts_directory()) .into_iter() .filter(|e| match e { @@ -26,3 +29,22 @@ pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> { println!("{:?}", blog_posts); Ok(()) } + +/// Delete everything inside the output directory and return the path to that directory. +async fn get_output_directory(config: &Config) -> Result { + let output_directory = config.get_output_directory(); + if !output_directory.exists() { + tokio::fs::create_dir(&output_directory).await?; + } else { + let mut existing_entries = tokio::fs::read_dir(&output_directory).await?; + while let Some(entry) = existing_entries.next_entry().await? { + let file_type = entry.file_type().await?; + if file_type.is_dir() { + tokio::fs::remove_dir_all(entry.path()).await?; + } else { + tokio::fs::remove_file(entry.path()).await?; + } + } + } + Ok(output_directory) +} diff --git a/src/config/full.rs b/src/config/full.rs index c2aeb61..0e499f7 100644 --- a/src/config/full.rs +++ b/src/config/full.rs @@ -55,4 +55,8 @@ impl Config { pub(crate) fn get_posts_directory(&self) -> PathBuf { self.get_root_directory().join("posts") } + + pub(crate) fn get_output_directory(&self) -> PathBuf { + self.get_root_directory().join("output") + } } From a0c5b2d85259172a516b97f4624ee3c15f74f0b7 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 14:49:08 -0400 Subject: [PATCH 07/47] Don't use walkdir for getting the post directories. We are only iterating a single level of depth anyway, so read_dir is enough. --- src/command/build/runner.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index b6ed39a..ecb97b8 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -4,23 +4,14 @@ use crate::blog_post::BlogPost; use crate::cli::parameters::BuildArgs; use crate::config::Config; use crate::error::CustomError; -use walkdir::WalkDir; 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 = WalkDir::new(config.get_posts_directory()) - .into_iter() - .filter(|e| match e { - Ok(entry) if entry.depth() == 1 && entry.file_type().is_dir() => true, - Ok(_) => false, - Err(_) => true, - }) - .collect::, _>>()?; + let post_directories = get_post_directories(&config).await?; let load_jobs = post_directories .into_iter() - .map(walkdir::DirEntry::into_path) .map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path))); let mut blog_posts = Vec::new(); for job in load_jobs { @@ -48,3 +39,15 @@ async fn get_output_directory(config: &Config) -> Result { } Ok(output_directory) } + +async fn get_post_directories(config: &Config) -> Result, CustomError> { + let mut ret = Vec::new(); + let mut entries = tokio::fs::read_dir(config.get_posts_directory()).await?; + while let Some(entry) = entries.next_entry().await? { + let file_type = entry.file_type().await?; + if file_type.is_dir() { + ret.push(entry.path()); + } + } + Ok(ret) +} From 87d32323f2d55acfcb0cd8fd4053132199d8f23f Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 15:01:30 -0400 Subject: [PATCH 08/47] Add duster. --- Cargo.lock | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b1774c9..3f575eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "backtrace" version = "0.3.69" @@ -80,6 +86,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "bytes" version = "1.5.0" @@ -146,12 +170,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "duster" +version = "0.1.1" +source = "git+https://code.fizz.buzz/talexander/duster.git?branch=master#3428a3f5097c7d2cc252d1bfd9aae7771553ab69" +dependencies = [ + "nom 6.1.2", + "serde", + "serde_json", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "gimli" version = "0.28.0" @@ -186,6 +226,25 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.149" @@ -213,6 +272,19 @@ dependencies = [ "adler", ] +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "nom" version = "7.1.3" @@ -248,7 +320,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f0f8a2a6d31c3cac7ebf543d8cb2e8f648300462fc2f6b1a09cac10daf0387" dependencies = [ - "nom", + "nom 7.1.3", "walkdir", ] @@ -276,12 +348,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "same-file" version = "1.0.6" @@ -311,6 +395,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.3" @@ -320,6 +415,12 @@ dependencies = [ "serde", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.38" @@ -331,6 +432,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tokio" version = "1.33.0" @@ -389,6 +496,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "walkdir" version = "2.4.0" @@ -510,9 +623,16 @@ name = "writer" version = "0.0.1" dependencies = [ "clap", + "duster", "organic", "serde", "tokio", "toml", "walkdir", ] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/Cargo.toml b/Cargo.toml index 88be60f..74ed727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [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" } organic = "0.1.12" # | alloc, rc, serde_derive, unstable serde = { version = "1.0.189", default-features = false, features = ["std", "derive"] } From a510d0809f8e97d62a8c9e3d534c6a119049f722 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 15:06:31 -0400 Subject: [PATCH 09/47] Add serde_json. --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3f575eb..fe7fb62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -626,6 +626,7 @@ dependencies = [ "duster", "organic", "serde", + "serde_json", "tokio", "toml", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 74ed727..eadc245 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ duster = { git = "https://code.fizz.buzz/talexander/duster.git", branch = "maste 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" From b72aec9d2057175fa44c8e5245d2bfe33a78a2c2 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 15:31:45 -0400 Subject: [PATCH 10/47] Starting a struct for what will be passed as the context into dust. --- src/blog_post/mod.rs | 1 + src/blog_post/render_context.rs | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 src/blog_post/render_context.rs diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index 9d44a74..687edec 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -1,2 +1,3 @@ mod definition; +mod render_context; pub(crate) use definition::BlogPost; diff --git a/src/blog_post/render_context.rs b/src/blog_post/render_context.rs new file mode 100644 index 0000000..e2562d0 --- /dev/null +++ b/src/blog_post/render_context.rs @@ -0,0 +1,8 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "blog_post")] +pub(crate) struct RenderBlogPost { + id: String, +} From 0b6900eeca25e49498648e0a8d8fe3834065fc5d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 16:01:42 -0400 Subject: [PATCH 11/47] Serialize the RenderBlogPost to JSON. This struct still does not contain anything, but I'm just setting up the skeleton for this code. --- src/blog_post/convert.rs | 8 ++++++++ src/blog_post/definition.rs | 2 +- src/blog_post/mod.rs | 2 ++ src/blog_post/render_context.rs | 2 +- src/command/build/runner.rs | 31 +++++++++++++++++++++---------- src/error/error.rs | 7 +++++++ 6 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 src/blog_post/convert.rs diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs new file mode 100644 index 0000000..128dd04 --- /dev/null +++ b/src/blog_post/convert.rs @@ -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(), + } +} diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs index 5179858..087beef 100644 --- a/src/blog_post/definition.rs +++ b/src/blog_post/definition.rs @@ -8,7 +8,7 @@ use crate::error::CustomError; #[derive(Debug)] pub(crate) struct BlogPost { - id: String, + pub(crate) id: String, } impl BlogPost { diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index 687edec..0962dda 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -1,3 +1,5 @@ +mod convert; mod definition; mod render_context; +pub(crate) use convert::convert_blog_post_to_render_context; pub(crate) use definition::BlogPost; diff --git a/src/blog_post/render_context.rs b/src/blog_post/render_context.rs index e2562d0..27fe4e4 100644 --- a/src/blog_post/render_context.rs +++ b/src/blog_post/render_context.rs @@ -4,5 +4,5 @@ use serde::Serialize; #[serde(tag = "type")] #[serde(rename = "blog_post")] pub(crate) struct RenderBlogPost { - id: String, + pub(crate) id: String, } diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index ecb97b8..f665bc5 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use crate::blog_post::convert_blog_post_to_render_context; use crate::blog_post::BlogPost; use crate::cli::parameters::BuildArgs; use crate::config::Config; @@ -7,17 +8,14 @@ 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??); - } + let blog_posts = load_blog_posts(&config).await?; println!("{:?}", blog_posts); + + for blog_post in &blog_posts { + let render_context = convert_blog_post_to_render_context(blog_post); + println!("{}", serde_json::to_string(&render_context)?); + } + Ok(()) } @@ -51,3 +49,16 @@ async fn get_post_directories(config: &Config) -> Result, CustomErr } Ok(ret) } + +async fn load_blog_posts(config: &Config) -> Result, 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) +} diff --git a/src/error/error.rs b/src/error/error.rs index 130f283..78f109f 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -6,6 +6,7 @@ pub(crate) enum CustomError { TomlDeserialize(toml::de::Error), WalkDir(walkdir::Error), Tokio(tokio::task::JoinError), + Serde(serde_json::Error), } impl From for CustomError { @@ -43,3 +44,9 @@ impl From for CustomError { CustomError::Tokio(value) } } + +impl From for CustomError { + fn from(value: serde_json::Error) -> Self { + CustomError::Serde(value) + } +} From 5f34cb2dd5c602575dd4aba62e8793666d7ddcaa Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 16:10:41 -0400 Subject: [PATCH 12/47] Creating a SiteRenderer struct to handle the logic for invoking dust. --- src/command/build/mod.rs | 1 + src/command/build/render.rs | 21 +++++++++++++++++++++ src/command/build/runner.rs | 13 ++++++------- 3 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 src/command/build/render.rs diff --git a/src/command/build/mod.rs b/src/command/build/mod.rs index 2b7c076..8edcb15 100644 --- a/src/command/build/mod.rs +++ b/src/command/build/mod.rs @@ -1,3 +1,4 @@ +mod render; mod runner; pub(crate) use runner::build_site; diff --git a/src/command/build/render.rs b/src/command/build/render.rs new file mode 100644 index 0000000..cc47f27 --- /dev/null +++ b/src/command/build/render.rs @@ -0,0 +1,21 @@ +use std::path::PathBuf; + +use crate::blog_post::convert_blog_post_to_render_context; +use crate::blog_post::BlogPost; +use crate::error::CustomError; + +pub(crate) struct SiteRenderer { + pub(crate) output_directory: PathBuf, + pub(crate) blog_posts: Vec, +} + +impl SiteRenderer { + pub(crate) async fn render_blog_posts(&self) -> Result<(), CustomError> { + for blog_post in &self.blog_posts { + let render_context = convert_blog_post_to_render_context(blog_post); + println!("{}", serde_json::to_string(&render_context)?); + } + + Ok(()) + } +} diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index f665bc5..8b859e5 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -1,20 +1,19 @@ use std::path::PathBuf; -use crate::blog_post::convert_blog_post_to_render_context; 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 blog_posts = load_blog_posts(&config).await?; - println!("{:?}", blog_posts); - - for blog_post in &blog_posts { - let render_context = convert_blog_post_to_render_context(blog_post); - println!("{}", serde_json::to_string(&render_context)?); - } + let renderer = SiteRenderer { + output_directory: get_output_directory(&config).await?, + blog_posts, + }; + renderer.render_blog_posts().await?; Ok(()) } From 24bac982f132f69a85b56d7e0ef756aed1ea6adb Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 16:26:43 -0400 Subject: [PATCH 13/47] Starting to create the renderer integrations. These are the layer directly over dust which can be used by anything, not just blog posts. --- src/command/build/render.rs | 3 +++ src/main.rs | 1 + src/render/duster_renderer.rs | 24 ++++++++++++++++++++++++ src/render/mod.rs | 3 +++ src/render/renderer_integration.rs | 15 +++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 src/render/duster_renderer.rs create mode 100644 src/render/mod.rs create mode 100644 src/render/renderer_integration.rs diff --git a/src/command/build/render.rs b/src/command/build/render.rs index cc47f27..7a35346 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use crate::blog_post::convert_blog_post_to_render_context; use crate::blog_post::BlogPost; use crate::error::CustomError; +use crate::render::DusterRenderer; pub(crate) struct SiteRenderer { pub(crate) output_directory: PathBuf, @@ -16,6 +17,8 @@ impl SiteRenderer { println!("{}", serde_json::to_string(&render_context)?); } + let mut renderer_integration = DusterRenderer {}; + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 124c99b..6e1fb2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod cli; mod command; mod config; mod error; +mod render; fn main() -> Result { let rt = tokio::runtime::Runtime::new()?; diff --git a/src/render/duster_renderer.rs b/src/render/duster_renderer.rs new file mode 100644 index 0000000..ac1cb2d --- /dev/null +++ b/src/render/duster_renderer.rs @@ -0,0 +1,24 @@ +use super::renderer_integration::RendererIntegration; + +pub(crate) struct DusterRenderer {} + +impl RendererIntegration for DusterRenderer { + fn load_templates(&mut self, dust_templates: I) -> Result<(), crate::error::CustomError> + where + I: Iterator, + P: Into, + { + todo!() + } + + fn render

( + &self, + context: &str, + build_directory: P, + ) -> Result + where + P: AsRef, + { + todo!() + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..389e21b --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,3 @@ +mod duster_renderer; +mod renderer_integration; +pub(crate) use duster_renderer::DusterRenderer; diff --git a/src/render/renderer_integration.rs b/src/render/renderer_integration.rs new file mode 100644 index 0000000..a854ac0 --- /dev/null +++ b/src/render/renderer_integration.rs @@ -0,0 +1,15 @@ +use std::path::Path; +use std::path::PathBuf; + +use crate::error::CustomError; + +pub trait RendererIntegration { + fn load_templates(&mut self, dust_templates: I) -> Result<(), CustomError> + where + I: Iterator, + P: Into; + + fn render

(&self, context: &str, build_directory: P) -> Result + where + P: AsRef; +} From aed88cf05a14c24fea351de6932cad3099d8867f Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 16:28:54 -0400 Subject: [PATCH 14/47] Add include_dir. This will let us embed the default versions of templates, stylesheets, javascript, etc into the binary. Naturally, we will eventually support overriding the defaults. --- Cargo.lock | 20 ++++++++++++++++++++ Cargo.toml | 4 +--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe7fb62..8ce1ec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index eadc245..cefc0b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" From fc5342adce49664bdc9ee4fd0431aa2b45a9928b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 16:40:58 -0400 Subject: [PATCH 15/47] Make the renderer a bit more generic. --- src/command/build/render.rs | 6 +++--- src/render/duster_renderer.rs | 19 +++++++++++-------- src/render/mod.rs | 1 + src/render/renderer_integration.rs | 9 +++++---- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 7a35346..5b4ce4c 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -4,6 +4,7 @@ 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; pub(crate) struct SiteRenderer { pub(crate) output_directory: PathBuf, @@ -12,13 +13,12 @@ pub(crate) struct SiteRenderer { impl SiteRenderer { pub(crate) async fn render_blog_posts(&self) -> Result<(), CustomError> { + let mut renderer_integration = DusterRenderer {}; for blog_post in &self.blog_posts { let render_context = convert_blog_post_to_render_context(blog_post); - println!("{}", serde_json::to_string(&render_context)?); + renderer_integration.render(render_context)?; } - let mut renderer_integration = DusterRenderer {}; - Ok(()) } } diff --git a/src/render/duster_renderer.rs b/src/render/duster_renderer.rs index ac1cb2d..435aab0 100644 --- a/src/render/duster_renderer.rs +++ b/src/render/duster_renderer.rs @@ -1,4 +1,6 @@ use super::renderer_integration::RendererIntegration; +use duster::renderer::DustRenderer; +use serde::Serialize; pub(crate) struct DusterRenderer {} @@ -8,17 +10,18 @@ impl RendererIntegration for DusterRenderer { I: Iterator, P: Into, { - todo!() + // TODO + Ok(()) } - fn render

( - &self, - context: &str, - build_directory: P, - ) -> Result + fn render(&self, context: C) -> Result where - P: AsRef, + C: Serialize, { - todo!() + let mut dust_renderer = DustRenderer::new(); + println!("{}", serde_json::to_string(&context)?); + + // TODO + Ok("".to_owned()) } } diff --git a/src/render/mod.rs b/src/render/mod.rs index 389e21b..b6bd3a1 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,3 +1,4 @@ mod duster_renderer; mod renderer_integration; pub(crate) use duster_renderer::DusterRenderer; +pub(crate) use renderer_integration::RendererIntegration; diff --git a/src/render/renderer_integration.rs b/src/render/renderer_integration.rs index a854ac0..4fbbba1 100644 --- a/src/render/renderer_integration.rs +++ b/src/render/renderer_integration.rs @@ -1,15 +1,16 @@ -use std::path::Path; use std::path::PathBuf; +use serde::Serialize; + use crate::error::CustomError; -pub trait RendererIntegration { +pub(crate) trait RendererIntegration { fn load_templates(&mut self, dust_templates: I) -> Result<(), CustomError> where I: Iterator, P: Into; - fn render

(&self, context: &str, build_directory: P) -> Result + fn render(&self, context: C) -> Result where - P: AsRef; + C: Serialize; } From ce0819e85b25269fff8e6999e4cd568311918d3a Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 17:31:12 -0400 Subject: [PATCH 16/47] Feeding the templates into the renderer integration. --- default_environment/templates/html/main.dust | 9 ++++ src/command/build/render.rs | 53 ++++++++++++++++++-- src/command/build/runner.rs | 5 +- src/error/error.rs | 17 +++++++ src/render/duster_renderer.rs | 13 +++-- src/render/renderer_integration.rs | 8 ++- 6 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 default_environment/templates/html/main.dust diff --git a/default_environment/templates/html/main.dust b/default_environment/templates/html/main.dust new file mode 100644 index 0000000..2d07f56 --- /dev/null +++ b/default_environment/templates/html/main.dust @@ -0,0 +1,9 @@ + + + + + + +

Hello world!

+ + diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 5b4ce4c..cb2224d 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -1,19 +1,51 @@ +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 { - pub(crate) output_directory: PathBuf, - pub(crate) blog_posts: Vec, + output_directory: PathBuf, + blog_posts: Vec, } impl SiteRenderer { + pub(crate) fn new>( + output_directory: P, + blog_posts: Vec, + ) -> SiteRenderer { + SiteRenderer { + output_directory: output_directory.into(), + blog_posts, + } + } pub(crate) async fn render_blog_posts(&self) -> Result<(), CustomError> { - let mut renderer_integration = DusterRenderer {}; + 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()); + } + + for entry in main_template { + load_template_from_include_dir(&mut renderer_integration, entry)?; + } + + for entry in other_templates { + load_template_from_include_dir(&mut renderer_integration, entry)?; + } + for blog_post in &self.blog_posts { let render_context = convert_blog_post_to_render_context(blog_post); renderer_integration.render(render_context)?; @@ -22,3 +54,18 @@ impl SiteRenderer { Ok(()) } } + +fn load_template_from_include_dir( + renderer_integration: &mut RI, + entry: &include_dir::File<'_>, +) -> Result<(), 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())?; + renderer_integration.load_template(name, contents)?; + Ok(()) +} diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index 8b859e5..7f970bd 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -9,10 +9,7 @@ use crate::error::CustomError; pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> { let config = Config::load_from_file(args.config).await?; let blog_posts = load_blog_posts(&config).await?; - let renderer = SiteRenderer { - output_directory: get_output_directory(&config).await?, - blog_posts, - }; + let renderer = SiteRenderer::new(get_output_directory(&config).await?, blog_posts); renderer.render_blog_posts().await?; Ok(()) diff --git a/src/error/error.rs b/src/error/error.rs index 78f109f..5daa7d6 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -1,3 +1,6 @@ +use std::str::Utf8Error; +use std::string::FromUtf8Error; + #[derive(Debug)] pub(crate) enum CustomError { Static(&'static str), @@ -7,6 +10,8 @@ pub(crate) enum CustomError { WalkDir(walkdir::Error), Tokio(tokio::task::JoinError), Serde(serde_json::Error), + Utf8(Utf8Error), + FromUtf8(FromUtf8Error), } impl From for CustomError { @@ -50,3 +55,15 @@ impl From for CustomError { CustomError::Serde(value) } } + +impl From for CustomError { + fn from(value: Utf8Error) -> Self { + CustomError::Utf8(value) + } +} + +impl From for CustomError { + fn from(value: FromUtf8Error) -> Self { + CustomError::FromUtf8(value) + } +} diff --git a/src/render/duster_renderer.rs b/src/render/duster_renderer.rs index 435aab0..ec19b6a 100644 --- a/src/render/duster_renderer.rs +++ b/src/render/duster_renderer.rs @@ -4,13 +4,18 @@ use serde::Serialize; pub(crate) struct DusterRenderer {} +impl DusterRenderer { + pub(crate) fn new() -> DusterRenderer { + DusterRenderer {} + } +} + impl RendererIntegration for DusterRenderer { - fn load_templates(&mut self, dust_templates: I) -> Result<(), crate::error::CustomError> + fn load_template(&mut self, name: N, contents: C) -> Result<(), crate::error::CustomError> where - I: Iterator, - P: Into, + N: Into, + C: Into, { - // TODO Ok(()) } diff --git a/src/render/renderer_integration.rs b/src/render/renderer_integration.rs index 4fbbba1..3087739 100644 --- a/src/render/renderer_integration.rs +++ b/src/render/renderer_integration.rs @@ -1,14 +1,12 @@ -use std::path::PathBuf; - use serde::Serialize; use crate::error::CustomError; pub(crate) trait RendererIntegration { - fn load_templates(&mut self, dust_templates: I) -> Result<(), CustomError> + fn load_template(&mut self, name: N, contents: C) -> Result<(), CustomError> where - I: Iterator, - P: Into; + N: Into, + C: Into; fn render(&self, context: C) -> Result where From 58aba8efd5d4d82243ad4ce21edac1720c4def36 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 17:37:27 -0400 Subject: [PATCH 17/47] Invoking the compile function. I am going to have to address the lifetime issue of "compiled" duster templates borrowing the input str. --- src/error/error.rs | 7 +++++++ src/render/duster_renderer.rs | 9 ++++++--- src/render/renderer_integration.rs | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/error/error.rs b/src/error/error.rs index 5daa7d6..245fe5a 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -12,6 +12,7 @@ pub(crate) enum CustomError { Serde(serde_json::Error), Utf8(Utf8Error), FromUtf8(FromUtf8Error), + DusterCompile(duster::renderer::CompileError), } impl From for CustomError { @@ -67,3 +68,9 @@ impl From for CustomError { CustomError::FromUtf8(value) } } + +impl From for CustomError { + fn from(value: duster::renderer::CompileError) -> Self { + CustomError::DusterCompile(value) + } +} diff --git a/src/render/duster_renderer.rs b/src/render/duster_renderer.rs index ec19b6a..492d84a 100644 --- a/src/render/duster_renderer.rs +++ b/src/render/duster_renderer.rs @@ -1,3 +1,5 @@ +use crate::error::CustomError; + use super::renderer_integration::RendererIntegration; use duster::renderer::DustRenderer; use serde::Serialize; @@ -11,15 +13,16 @@ impl DusterRenderer { } impl RendererIntegration for DusterRenderer { - fn load_template(&mut self, name: N, contents: C) -> Result<(), crate::error::CustomError> + fn load_template(&mut self, name: N, contents: C) -> Result<(), CustomError> where N: Into, - C: Into, + C: AsRef, { + let compiled_template = duster::renderer::compile_template(contents.as_ref())?; Ok(()) } - fn render(&self, context: C) -> Result + fn render(&self, context: C) -> Result where C: Serialize, { diff --git a/src/render/renderer_integration.rs b/src/render/renderer_integration.rs index 3087739..7d6591d 100644 --- a/src/render/renderer_integration.rs +++ b/src/render/renderer_integration.rs @@ -6,9 +6,9 @@ pub(crate) trait RendererIntegration { fn load_template(&mut self, name: N, contents: C) -> Result<(), CustomError> where N: Into, - C: Into; + C: AsRef; - fn render(&self, context: C) -> Result + fn render(&self, context: C) -> Result where C: Serialize; } From 043cc5eda49bfbbc2b4da40d7ac6a95155088e31 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 17:43:30 -0400 Subject: [PATCH 18/47] I think I have worked around the lifetime issue by keeping references to the intermediate str's. --- src/command/build/render.rs | 28 +++++++++++++++++----------- src/render/duster_renderer.rs | 23 +++++++++++++---------- src/render/renderer_integration.rs | 7 ++----- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/command/build/render.rs b/src/command/build/render.rs index cb2224d..cac8670 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -38,12 +38,20 @@ impl SiteRenderer { return Err("Expect exactly 1 main.dust template file.".into()); } - for entry in main_template { - load_template_from_include_dir(&mut renderer_integration, entry)?; - } + 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 entry in other_templates { - load_template_from_include_dir(&mut renderer_integration, entry)?; + for (name, contents) in decoded_templates { + renderer_integration.load_template(name, contents)?; } for blog_post in &self.blog_posts { @@ -55,10 +63,9 @@ impl SiteRenderer { } } -fn load_template_from_include_dir( - renderer_integration: &mut RI, - entry: &include_dir::File<'_>, -) -> Result<(), CustomError> { +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() @@ -66,6 +73,5 @@ fn load_template_from_include_dir( .to_str() .ok_or("All template filenames should be valid utf-8.")?; let contents = std::str::from_utf8(entry.contents())?; - renderer_integration.load_template(name, contents)?; - Ok(()) + Ok((name, contents)) } diff --git a/src/render/duster_renderer.rs b/src/render/duster_renderer.rs index 492d84a..4c8a944 100644 --- a/src/render/duster_renderer.rs +++ b/src/render/duster_renderer.rs @@ -1,24 +1,27 @@ +use std::collections::HashMap; + use crate::error::CustomError; use super::renderer_integration::RendererIntegration; use duster::renderer::DustRenderer; use serde::Serialize; -pub(crate) struct DusterRenderer {} +pub(crate) struct DusterRenderer<'a> { + templates: HashMap<&'a str, duster::parser::Template<'a>>, +} -impl DusterRenderer { - pub(crate) fn new() -> DusterRenderer { - DusterRenderer {} +impl<'a> DusterRenderer<'a> { + pub(crate) fn new() -> DusterRenderer<'a> { + DusterRenderer { + templates: HashMap::new(), + } } } -impl RendererIntegration for DusterRenderer { - fn load_template(&mut self, name: N, contents: C) -> Result<(), CustomError> - where - N: Into, - C: AsRef, - { +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(()) } diff --git a/src/render/renderer_integration.rs b/src/render/renderer_integration.rs index 7d6591d..ebf103c 100644 --- a/src/render/renderer_integration.rs +++ b/src/render/renderer_integration.rs @@ -2,11 +2,8 @@ use serde::Serialize; use crate::error::CustomError; -pub(crate) trait RendererIntegration { - fn load_template(&mut self, name: N, contents: C) -> Result<(), CustomError> - where - N: Into, - C: AsRef; +pub(crate) trait RendererIntegration<'a> { + fn load_template(&mut self, name: &'a str, contents: &'a str) -> Result<(), CustomError>; fn render(&self, context: C) -> Result where From 586fd8a066701ac81208c404d5ea437cd2bd70ad Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 18:30:01 -0400 Subject: [PATCH 19/47] Getting rendered output from duster. --- src/command/build/render.rs | 3 ++- src/error/error.rs | 7 +++++++ src/render/duster_renderer.rs | 16 ++++++++++------ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/command/build/render.rs b/src/command/build/render.rs index cac8670..0324619 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -56,7 +56,8 @@ impl SiteRenderer { for blog_post in &self.blog_posts { let render_context = convert_blog_post_to_render_context(blog_post); - renderer_integration.render(render_context)?; + let rendered_output = renderer_integration.render(render_context)?; + println!("Rendered: {}", rendered_output); } Ok(()) diff --git a/src/error/error.rs b/src/error/error.rs index 245fe5a..1311682 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -13,6 +13,7 @@ pub(crate) enum CustomError { Utf8(Utf8Error), FromUtf8(FromUtf8Error), DusterCompile(duster::renderer::CompileError), + DusterRender(duster::renderer::RenderError), } impl From for CustomError { @@ -74,3 +75,9 @@ impl From for CustomError { CustomError::DusterCompile(value) } } + +impl From for CustomError { + fn from(value: duster::renderer::RenderError) -> Self { + CustomError::DusterRender(value) + } +} diff --git a/src/render/duster_renderer.rs b/src/render/duster_renderer.rs index 4c8a944..5602731 100644 --- a/src/render/duster_renderer.rs +++ b/src/render/duster_renderer.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use crate::error::CustomError; use super::renderer_integration::RendererIntegration; -use duster::renderer::DustRenderer; use serde::Serialize; pub(crate) struct DusterRenderer<'a> { @@ -29,10 +28,15 @@ impl<'a> RendererIntegration<'a> for DusterRenderer<'a> { where C: Serialize, { - let mut dust_renderer = DustRenderer::new(); - println!("{}", serde_json::to_string(&context)?); - - // TODO - Ok("".to_owned()) + 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) } } From 199621b6f113f5b40220a288deaeffa191bc0ab6 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 22 Oct 2023 18:39:05 -0400 Subject: [PATCH 20/47] Remove the separation of the main template. I don't think this is necessary, and it certainly isn't necessary at this level. --- src/command/build/render.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 0324619..3df81cc 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -30,21 +30,22 @@ impl SiteRenderer { 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 + let sources: 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 { + .collect(); + if sources + .iter() + .filter(|f| f.path().file_stem() == Some(OsStr::new("main"))) + .count() + != 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 { + let mut decoded_templates = Vec::with_capacity(sources.len()); + for entry in sources { decoded_templates.push(build_name_contents_pairs(entry)?); } decoded_templates From e543a5db741902bc31fab32fa3be75f0860cfdd6 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 23 Oct 2023 16:03:37 -0400 Subject: [PATCH 21/47] Starting to introduce a BlogPostPage struct. Blog posts are going to be constructed of multiple documents each forming their own page. This will allow me to link to supporting documents without having to promote them to their own pages. --- src/blog_post/convert.rs | 4 +--- src/blog_post/definition.rs | 16 ++++++++++++++++ src/blog_post/mod.rs | 2 ++ src/blog_post/page.rs | 24 ++++++++++++++++++++++++ src/blog_post/render_context.rs | 8 +++++++- src/error/error.rs | 7 +++++++ 6 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 src/blog_post/page.rs diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 128dd04..a2de168 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -2,7 +2,5 @@ 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(), - } + RenderBlogPost::new(post.id.clone()) } diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs index 087beef..7589986 100644 --- a/src/blog_post/definition.rs +++ b/src/blog_post/definition.rs @@ -6,9 +6,12 @@ use walkdir::WalkDir; use crate::error::CustomError; +use super::BlogPostPage; + #[derive(Debug)] pub(crate) struct BlogPost { pub(crate) id: String, + pages: Vec, } impl BlogPost { @@ -39,8 +42,21 @@ impl BlogPost { ret }; + let pages = { + let mut ret = Vec::new(); + for (real_path, _contents, parsed_document) in parsed_org_files { + let relative_to_post_dir_path = real_path.strip_prefix(post_dir)?; + ret.push(BlogPostPage::new( + relative_to_post_dir_path, + parsed_document, + )?); + } + ret + }; + Ok(BlogPost { id: post_id.to_string_lossy().into_owned(), + pages, }) } inner(root_dir.as_ref(), post_dir.as_ref()).await diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index 0962dda..9ec4e23 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -1,5 +1,7 @@ mod convert; mod definition; +mod page; mod render_context; pub(crate) use convert::convert_blog_post_to_render_context; pub(crate) use definition::BlogPost; +pub(crate) use page::BlogPostPage; diff --git a/src/blog_post/page.rs b/src/blog_post/page.rs new file mode 100644 index 0000000..21e7c3f --- /dev/null +++ b/src/blog_post/page.rs @@ -0,0 +1,24 @@ +use std::path::PathBuf; + +use crate::error::CustomError; + +#[derive(Debug)] +pub(crate) struct BlogPostPage { + /// Relative path from the root of the blog post. + path: PathBuf, + + title: String, +} + +impl BlogPostPage { + pub(crate) fn new>( + path: P, + document: organic::types::Document<'_>, + ) -> Result { + let path = path.into(); + Ok(BlogPostPage { + path, + title: "".to_owned(), + }) + } +} diff --git a/src/blog_post/render_context.rs b/src/blog_post/render_context.rs index 27fe4e4..9163d90 100644 --- a/src/blog_post/render_context.rs +++ b/src/blog_post/render_context.rs @@ -4,5 +4,11 @@ use serde::Serialize; #[serde(tag = "type")] #[serde(rename = "blog_post")] pub(crate) struct RenderBlogPost { - pub(crate) id: String, + id: String, +} + +impl RenderBlogPost { + pub(crate) fn new(id: String) -> RenderBlogPost { + RenderBlogPost { id } + } } diff --git a/src/error/error.rs b/src/error/error.rs index 1311682..9707bdb 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -14,6 +14,7 @@ pub(crate) enum CustomError { FromUtf8(FromUtf8Error), DusterCompile(duster::renderer::CompileError), DusterRender(duster::renderer::RenderError), + PathStripPrefix(std::path::StripPrefixError), } impl From for CustomError { @@ -81,3 +82,9 @@ impl From for CustomError { CustomError::DusterRender(value) } } + +impl From for CustomError { + fn from(value: std::path::StripPrefixError) -> Self { + CustomError::PathStripPrefix(value) + } +} From dc233d26b1a8c4353e74ec1bda676583395c6b76 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 23 Oct 2023 18:39:54 -0400 Subject: [PATCH 22/47] Store the title in BlogPostPage. --- Cargo.lock | 2 -- Cargo.toml | 4 +++- src/blog_post/page.rs | 17 +++++++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ce1ec8..44e485f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,8 +336,6 @@ dependencies = [ [[package]] name = "organic" version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f0f8a2a6d31c3cac7ebf543d8cb2e8f648300462fc2f6b1a09cac10daf0387" dependencies = [ "nom 7.1.3", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index cefc0b4..4026897 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,9 @@ edition = "2021" 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" +# TODO: This is temporary to work on the latest organic code. Eventually switch back to using the published crate. +organic = { path = "../organic" } +# organic = "0.1.12" 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"] } diff --git a/src/blog_post/page.rs b/src/blog_post/page.rs index 21e7c3f..a0d94b7 100644 --- a/src/blog_post/page.rs +++ b/src/blog_post/page.rs @@ -7,7 +7,7 @@ pub(crate) struct BlogPostPage { /// Relative path from the root of the blog post. path: PathBuf, - title: String, + title: Option, } impl BlogPostPage { @@ -18,7 +18,20 @@ impl BlogPostPage { let path = path.into(); Ok(BlogPostPage { path, - title: "".to_owned(), + title: get_title(&document), }) } } + +fn get_title(document: &organic::types::Document<'_>) -> Option { + organic::types::AstNode::from(document) + .iter_all_ast_nodes() + .filter_map(|node| match node { + organic::types::AstNode::Keyword(kw) if kw.key.eq_ignore_ascii_case("title") => { + Some(kw) + } + _ => None, + }) + .last() + .map(|kw| kw.value.to_owned()) +} From 2b6f86d4e9c19e1b7788ea8900c930828cd0055d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 23 Oct 2023 20:30:43 -0400 Subject: [PATCH 23/47] Switch to rendering blog post pages instead of blog posts. --- src/blog_post/convert.rs | 10 +++++++--- src/blog_post/definition.rs | 2 +- src/blog_post/mod.rs | 2 +- src/blog_post/page.rs | 11 +++++++++-- src/blog_post/render_context.rs | 12 ++++++------ src/command/build/render.rs | 22 ++++++++++++++++++---- 6 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index a2de168..8c17621 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -1,6 +1,10 @@ -use super::render_context::RenderBlogPost; +use super::render_context::RenderBlogPostPage; use super::BlogPost; +use super::BlogPostPage; -pub(crate) fn convert_blog_post_to_render_context(post: &BlogPost) -> RenderBlogPost { - RenderBlogPost::new(post.id.clone()) +pub(crate) fn convert_blog_post_page_to_render_context( + _post: &BlogPost, + page: &BlogPostPage, +) -> RenderBlogPostPage { + RenderBlogPostPage::new(page.title.clone()) } diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs index 7589986..277c439 100644 --- a/src/blog_post/definition.rs +++ b/src/blog_post/definition.rs @@ -11,7 +11,7 @@ use super::BlogPostPage; #[derive(Debug)] pub(crate) struct BlogPost { pub(crate) id: String, - pages: Vec, + pub(crate) pages: Vec, } impl BlogPost { diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index 9ec4e23..bd4e010 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -2,6 +2,6 @@ mod convert; mod definition; mod page; mod render_context; -pub(crate) use convert::convert_blog_post_to_render_context; +pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; pub(crate) use page::BlogPostPage; diff --git a/src/blog_post/page.rs b/src/blog_post/page.rs index a0d94b7..8c56412 100644 --- a/src/blog_post/page.rs +++ b/src/blog_post/page.rs @@ -5,9 +5,9 @@ use crate::error::CustomError; #[derive(Debug)] pub(crate) struct BlogPostPage { /// Relative path from the root of the blog post. - path: PathBuf, + pub(crate) path: PathBuf, - title: Option, + pub(crate) title: Option, } impl BlogPostPage { @@ -21,6 +21,13 @@ impl BlogPostPage { title: get_title(&document), }) } + + /// Get the output path relative to the post directory. + pub(crate) fn get_output_path(&self) -> PathBuf { + let mut ret = self.path.clone(); + ret.set_extension("html"); + ret + } } fn get_title(document: &organic::types::Document<'_>) -> Option { diff --git a/src/blog_post/render_context.rs b/src/blog_post/render_context.rs index 9163d90..fb56b61 100644 --- a/src/blog_post/render_context.rs +++ b/src/blog_post/render_context.rs @@ -2,13 +2,13 @@ use serde::Serialize; #[derive(Debug, Serialize)] #[serde(tag = "type")] -#[serde(rename = "blog_post")] -pub(crate) struct RenderBlogPost { - id: String, +#[serde(rename = "blog_post_page")] +pub(crate) struct RenderBlogPostPage { + title: Option, } -impl RenderBlogPost { - pub(crate) fn new(id: String) -> RenderBlogPost { - RenderBlogPost { id } +impl RenderBlogPostPage { + pub(crate) fn new(title: Option) -> RenderBlogPostPage { + RenderBlogPostPage { title } } } diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 3df81cc..1ce1d82 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -4,7 +4,7 @@ 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::convert_blog_post_page_to_render_context; use crate::blog_post::BlogPost; use crate::error::CustomError; use crate::render::DusterRenderer; @@ -56,9 +56,23 @@ impl SiteRenderer { } 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); + for blog_post_page in &blog_post.pages { + let output_path = self + .output_directory + .join("posts") + .join(&blog_post.id) + .join(blog_post_page.get_output_path()); + println!("Output path: {:?}", output_path); + let render_context = + convert_blog_post_page_to_render_context(blog_post, blog_post_page); + let rendered_output = renderer_integration.render(render_context)?; + println!("Rendered: {}", rendered_output); + 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?; + } } Ok(()) From 178ce877bc30152c795bc663bac503134eee4a3d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 23 Oct 2023 21:51:15 -0400 Subject: [PATCH 24/47] Render the head for the page. --- .../templates/html/blog_post_page.dust | 5 ++ default_environment/templates/html/main.dust | 10 ++- src/blog_post/convert.rs | 4 +- src/blog_post/mod.rs | 1 + src/blog_post/render_context.rs | 36 +++++++++- src/command/build/render.rs | 65 +++++++++++++++++-- src/command/build/runner.rs | 2 +- src/config/full.rs | 8 +++ src/config/raw.rs | 4 ++ 9 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 default_environment/templates/html/blog_post_page.dust diff --git a/default_environment/templates/html/blog_post_page.dust b/default_environment/templates/html/blog_post_page.dust new file mode 100644 index 0000000..eac6770 --- /dev/null +++ b/default_environment/templates/html/blog_post_page.dust @@ -0,0 +1,5 @@ +
+
+ +
+
diff --git a/default_environment/templates/html/main.dust b/default_environment/templates/html/main.dust index 2d07f56..eba851f 100644 --- a/default_environment/templates/html/main.dust +++ b/default_environment/templates/html/main.dust @@ -2,8 +2,16 @@ + {#global_settings.css_files}{/global_settings.css_files} + {#global_settings.js_files}{/global_settings.js_files} + {?global_settings.page_title}{global_settings.page_title}{/global_settings.page_title} -

Hello world!

+
+ {@select key=.type} + {@eq value="blog_post_page"}{>blog_post_page/}{/eq} + {@none}{!TODO: make this panic!}ERROR: Unrecognized page content type{/none} + {/select} +
diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 8c17621..7d2dcaa 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -1,3 +1,4 @@ +use super::render_context::GlobalSettings; use super::render_context::RenderBlogPostPage; use super::BlogPost; use super::BlogPostPage; @@ -5,6 +6,7 @@ use super::BlogPostPage; pub(crate) fn convert_blog_post_page_to_render_context( _post: &BlogPost, page: &BlogPostPage, + global_settings: GlobalSettings, ) -> RenderBlogPostPage { - RenderBlogPostPage::new(page.title.clone()) + RenderBlogPostPage::new(global_settings, page.title.clone()) } diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index bd4e010..2b6790c 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -5,3 +5,4 @@ mod render_context; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; pub(crate) use page::BlogPostPage; +pub(crate) use render_context::GlobalSettings; diff --git a/src/blog_post/render_context.rs b/src/blog_post/render_context.rs index fb56b61..8015ece 100644 --- a/src/blog_post/render_context.rs +++ b/src/blog_post/render_context.rs @@ -1,14 +1,46 @@ use serde::Serialize; +/// The settings that a "global" to a single dustjs render. +#[derive(Debug, Serialize)] +pub(crate) struct GlobalSettings { + /// The title that goes in the html tag in the <head>. + page_title: Option<String>, + css_files: Vec<String>, + js_files: Vec<String>, +} + +impl GlobalSettings { + pub(crate) fn new( + page_title: Option<String>, + css_files: Vec<String>, + js_files: Vec<String>, + ) -> GlobalSettings { + GlobalSettings { + page_title, + css_files, + js_files, + } + } +} + #[derive(Debug, Serialize)] #[serde(tag = "type")] #[serde(rename = "blog_post_page")] pub(crate) struct RenderBlogPostPage { + global_settings: GlobalSettings, + + /// The title that will be shown visibly on the page. title: Option<String>, } impl RenderBlogPostPage { - pub(crate) fn new(title: Option<String>) -> RenderBlogPostPage { - RenderBlogPostPage { title } + pub(crate) fn new( + global_settings: GlobalSettings, + title: Option<String>, + ) -> RenderBlogPostPage { + RenderBlogPostPage { + global_settings, + title, + } } } diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 1ce1d82..1acd6af 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -1,4 +1,5 @@ use std::ffi::OsStr; +use std::path::Path; use std::path::PathBuf; use include_dir::include_dir; @@ -6,6 +7,8 @@ use include_dir::Dir; use crate::blog_post::convert_blog_post_page_to_render_context; use crate::blog_post::BlogPost; +use crate::blog_post::GlobalSettings; +use crate::config::Config; use crate::error::CustomError; use crate::render::DusterRenderer; use crate::render::RendererIntegration; @@ -27,7 +30,7 @@ impl SiteRenderer { blog_posts, } } - pub(crate) async fn render_blog_posts(&self) -> Result<(), CustomError> { + pub(crate) async fn render_blog_posts(&self, config: &Config) -> Result<(), CustomError> { let mut renderer_integration = DusterRenderer::new(); let sources: Vec<_> = MAIN_TEMPLATES @@ -62,9 +65,25 @@ impl SiteRenderer { .join("posts") .join(&blog_post.id) .join(blog_post_page.get_output_path()); - println!("Output path: {:?}", output_path); - let render_context = - convert_blog_post_page_to_render_context(blog_post, blog_post_page); + let css_files = vec![get_web_path( + config, + &self.output_directory, + &output_path, + "main.css", + )?]; + let js_files = vec![get_web_path( + config, + &self.output_directory, + &output_path, + "blog_post.js", + )?]; + let global_settings = + GlobalSettings::new(blog_post_page.title.clone(), css_files, js_files); + let render_context = convert_blog_post_page_to_render_context( + blog_post, + blog_post_page, + global_settings, + ); let rendered_output = renderer_integration.render(render_context)?; println!("Rendered: {}", rendered_output); let parent_directory = output_path @@ -91,3 +110,41 @@ fn build_name_contents_pairs<'a>( let contents = std::str::from_utf8(entry.contents())?; Ok((name, contents)) } + +fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>( + config: &Config, + output_directory: D, + containing_file: F, + path_from_web_root: P, +) -> Result<String, CustomError> { + let path_from_web_root = path_from_web_root.as_ref(); + if config.use_relative_paths() { + let output_directory = output_directory.as_ref(); + let containing_file = containing_file.as_ref(); + // Subtracting 1 from the depth to "remove" the file name. + let depth_from_web_root = containing_file + .strip_prefix(output_directory)? + .components() + .count() + - 1; + let prefix = "../".repeat(depth_from_web_root); + let final_path = PathBuf::from(prefix).join(path_from_web_root); + let final_string = final_path + .as_path() + .to_str() + .map(str::to_string) + .ok_or("Path should be valid utf-8.")?; + Ok(final_string) + } else { + let web_root = config + .get_web_root() + .ok_or("Must either use_relative_paths or set the web_root in the config.")?; + let final_path = PathBuf::from(web_root).join(path_from_web_root); + let final_string = final_path + .as_path() + .to_str() + .map(str::to_string) + .ok_or("Path should be valid utf-8.")?; + Ok(final_string) + } +} diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index 7f970bd..bb267f6 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -10,7 +10,7 @@ pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> { let config = Config::load_from_file(args.config).await?; let blog_posts = load_blog_posts(&config).await?; let renderer = SiteRenderer::new(get_output_directory(&config).await?, blog_posts); - renderer.render_blog_posts().await?; + renderer.render_blog_posts(&config).await?; Ok(()) } diff --git a/src/config/full.rs b/src/config/full.rs index 0e499f7..0f962aa 100644 --- a/src/config/full.rs +++ b/src/config/full.rs @@ -59,4 +59,12 @@ impl Config { pub(crate) fn get_output_directory(&self) -> PathBuf { self.get_root_directory().join("output") } + + pub(crate) fn use_relative_paths(&self) -> bool { + self.raw.use_relative_paths.unwrap_or(true) + } + + pub(crate) fn get_web_root(&self) -> Option<&str> { + self.raw.web_root.as_deref() + } } diff --git a/src/config/raw.rs b/src/config/raw.rs index 3d43bd5..b0d95cf 100644 --- a/src/config/raw.rs +++ b/src/config/raw.rs @@ -7,6 +7,8 @@ pub(crate) struct RawConfig { site_title: String, author: Option<String>, email: Option<String>, + pub(super) use_relative_paths: Option<bool>, + pub(super) web_root: Option<String>, } impl Default for RawConfig { @@ -15,6 +17,8 @@ impl Default for RawConfig { site_title: "My super awesome website".to_owned(), author: None, email: None, + use_relative_paths: None, + web_root: None, } } } From 3ac7826d2c68bfb325d962d433eea3360095dfb2 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Mon, 23 Oct 2023 22:10:26 -0400 Subject: [PATCH 25/47] Move the logic into convert_blog_post_page_to_render_context. I was writing it in the build command's rust files for convenience, but now its getting long enough to warrant moving it into its final location. --- src/blog_post/convert.rs | 71 ++++++++++++++++++++++++++++++++++--- src/blog_post/definition.rs | 2 +- src/blog_post/mod.rs | 1 - src/command/build/render.rs | 61 +++---------------------------- 4 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 7d2dcaa..baed94d 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -1,12 +1,75 @@ +use std::path::Path; +use std::path::PathBuf; + +use crate::config::Config; +use crate::error::CustomError; + use super::render_context::GlobalSettings; use super::render_context::RenderBlogPostPage; use super::BlogPost; use super::BlogPostPage; -pub(crate) fn convert_blog_post_page_to_render_context( +pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, _post: &BlogPost, page: &BlogPostPage, - global_settings: GlobalSettings, -) -> RenderBlogPostPage { - RenderBlogPostPage::new(global_settings, page.title.clone()) +) -> Result<RenderBlogPostPage, CustomError> { + let output_directory = output_directory.as_ref(); + let output_file = output_file.as_ref(); + let css_files = vec![get_web_path( + config, + output_directory, + output_file, + "main.css", + )?]; + let js_files = vec![get_web_path( + config, + output_directory, + output_file, + "blog_post.js", + )?]; + let global_settings = GlobalSettings::new(page.title.clone(), css_files, js_files); + + let ret = RenderBlogPostPage::new(global_settings, page.title.clone()); + Ok(ret) +} + +fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>( + config: &Config, + output_directory: D, + containing_file: F, + path_from_web_root: P, +) -> Result<String, CustomError> { + let path_from_web_root = path_from_web_root.as_ref(); + if config.use_relative_paths() { + let output_directory = output_directory.as_ref(); + let containing_file = containing_file.as_ref(); + // Subtracting 1 from the depth to "remove" the file name. + let depth_from_web_root = containing_file + .strip_prefix(output_directory)? + .components() + .count() + - 1; + let prefix = "../".repeat(depth_from_web_root); + let final_path = PathBuf::from(prefix).join(path_from_web_root); + let final_string = final_path + .as_path() + .to_str() + .map(str::to_string) + .ok_or("Path should be valid utf-8.")?; + Ok(final_string) + } else { + let web_root = config + .get_web_root() + .ok_or("Must either use_relative_paths or set the web_root in the config.")?; + let final_path = PathBuf::from(web_root).join(path_from_web_root); + let final_string = final_path + .as_path() + .to_str() + .map(str::to_string) + .ok_or("Path should be valid utf-8.")?; + Ok(final_string) + } } diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs index 277c439..96a3c7a 100644 --- a/src/blog_post/definition.rs +++ b/src/blog_post/definition.rs @@ -19,7 +19,7 @@ impl BlogPost { root_dir: R, post_dir: P, ) -> Result<BlogPost, CustomError> { - async fn inner(root_dir: &Path, post_dir: &Path) -> Result<BlogPost, CustomError> { + async fn inner(_root_dir: &Path, post_dir: &Path) -> Result<BlogPost, CustomError> { let post_id = post_dir .file_name() .expect("The post directory should have a name."); diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index 2b6790c..bd4e010 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -5,4 +5,3 @@ mod render_context; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; pub(crate) use page::BlogPostPage; -pub(crate) use render_context::GlobalSettings; diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 1acd6af..808d5b3 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -1,5 +1,4 @@ use std::ffi::OsStr; -use std::path::Path; use std::path::PathBuf; use include_dir::include_dir; @@ -7,7 +6,6 @@ use include_dir::Dir; use crate::blog_post::convert_blog_post_page_to_render_context; use crate::blog_post::BlogPost; -use crate::blog_post::GlobalSettings; use crate::config::Config; use crate::error::CustomError; use crate::render::DusterRenderer; @@ -65,25 +63,14 @@ impl SiteRenderer { .join("posts") .join(&blog_post.id) .join(blog_post_page.get_output_path()); - let css_files = vec![get_web_path( - config, - &self.output_directory, - &output_path, - "main.css", - )?]; - let js_files = vec![get_web_path( - config, - &self.output_directory, - &output_path, - "blog_post.js", - )?]; - let global_settings = - GlobalSettings::new(blog_post_page.title.clone(), css_files, js_files); + let render_context = convert_blog_post_page_to_render_context( + config, + &self.output_directory, + &output_path, blog_post, blog_post_page, - global_settings, - ); + )?; let rendered_output = renderer_integration.render(render_context)?; println!("Rendered: {}", rendered_output); let parent_directory = output_path @@ -110,41 +97,3 @@ fn build_name_contents_pairs<'a>( let contents = std::str::from_utf8(entry.contents())?; Ok((name, contents)) } - -fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>( - config: &Config, - output_directory: D, - containing_file: F, - path_from_web_root: P, -) -> Result<String, CustomError> { - let path_from_web_root = path_from_web_root.as_ref(); - if config.use_relative_paths() { - let output_directory = output_directory.as_ref(); - let containing_file = containing_file.as_ref(); - // Subtracting 1 from the depth to "remove" the file name. - let depth_from_web_root = containing_file - .strip_prefix(output_directory)? - .components() - .count() - - 1; - let prefix = "../".repeat(depth_from_web_root); - let final_path = PathBuf::from(prefix).join(path_from_web_root); - let final_string = final_path - .as_path() - .to_str() - .map(str::to_string) - .ok_or("Path should be valid utf-8.")?; - Ok(final_string) - } else { - let web_root = config - .get_web_root() - .ok_or("Must either use_relative_paths or set the web_root in the config.")?; - let final_path = PathBuf::from(web_root).join(path_from_web_root); - let final_string = final_path - .as_path() - .to_str() - .map(str::to_string) - .ok_or("Path should be valid utf-8.")?; - Ok(final_string) - } -} From 11bfb6836fc12f77ff2fbc993170d81cf4db6b3d Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Mon, 23 Oct 2023 22:38:00 -0400 Subject: [PATCH 26/47] Include a self-link for the blog. --- src/blog_post/convert.rs | 40 ++++++++++++++++++++++++++++++--- src/blog_post/render_context.rs | 4 ++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index baed94d..8a03dd6 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -31,8 +31,14 @@ pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef< "blog_post.js", )?]; let global_settings = GlobalSettings::new(page.title.clone(), css_files, js_files); + let link_to_blog_post = get_web_path( + config, + output_directory, + output_file, + output_file.strip_prefix(output_directory)?, + )?; - let ret = RenderBlogPostPage::new(global_settings, page.title.clone()); + let ret = RenderBlogPostPage::new(global_settings, page.title.clone(), Some(link_to_blog_post)); Ok(ret) } @@ -46,13 +52,15 @@ fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>( if config.use_relative_paths() { let output_directory = output_directory.as_ref(); let containing_file = containing_file.as_ref(); + let containing_file_relative_to_output_directory = + containing_file.strip_prefix(output_directory)?; // Subtracting 1 from the depth to "remove" the file name. - let depth_from_web_root = containing_file - .strip_prefix(output_directory)? + let depth_from_web_root = containing_file_relative_to_output_directory .components() .count() - 1; let prefix = "../".repeat(depth_from_web_root); + // TODO: It should be possible to strip some of the "../" components based on whether the path_from_web_root goes down the same path as the containing file. let final_path = PathBuf::from(prefix).join(path_from_web_root); let final_string = final_path .as_path() @@ -73,3 +81,29 @@ fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>( Ok(final_string) } } + +#[allow(dead_code)] +fn count_shared_steps<A: AsRef<Path>, B: AsRef<Path>>(left: A, right: B) -> usize { + let left = left.as_ref(); + let right = right.as_ref(); + left.components() + .zip(right.components()) + .position(|(l, r)| l != r) + .unwrap_or_else(|| left.components().count()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_count_shared_steps() { + assert_eq!(count_shared_steps("", ""), 0); + assert_eq!(count_shared_steps("foo.txt", "foo.txt"), 1); + assert_eq!(count_shared_steps("cat/foo.txt", "dog/foo.txt"), 0); + assert_eq!( + count_shared_steps("foo/bar/baz/lorem.txt", "foo/bar/ipsum/dolar.txt"), + 2 + ); + } +} diff --git a/src/blog_post/render_context.rs b/src/blog_post/render_context.rs index 8015ece..4085b9d 100644 --- a/src/blog_post/render_context.rs +++ b/src/blog_post/render_context.rs @@ -31,16 +31,20 @@ pub(crate) struct RenderBlogPostPage { /// The title that will be shown visibly on the page. title: Option<String>, + + self_link: Option<String>, } impl RenderBlogPostPage { pub(crate) fn new( global_settings: GlobalSettings, title: Option<String>, + self_link: Option<String>, ) -> RenderBlogPostPage { RenderBlogPostPage { global_settings, title, + self_link, } } } From 68cae57f16c8b6176fbd9e9283ee9babd9ad00fc Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Mon, 23 Oct 2023 22:50:43 -0400 Subject: [PATCH 27/47] Add a function to get the shared portion of a path. --- src/blog_post/convert.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 8a03dd6..63a9fa2 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -1,3 +1,4 @@ +use std::path::Component; use std::path::Path; use std::path::PathBuf; @@ -92,6 +93,17 @@ fn count_shared_steps<A: AsRef<Path>, B: AsRef<Path>>(left: A, right: B) -> usiz .unwrap_or_else(|| left.components().count()) } +#[allow(dead_code)] +fn get_shared_steps<'a>(left: &'a Path, right: &'a Path) -> impl Iterator<Item = Component<'a>> { + let foo: Vec<Component<'_>> = left + .components() + .zip(right.components()) + .take_while(|(l, r)| l == r) + .map(|(l, _r)| l) + .collect(); + foo.into_iter() +} + #[cfg(test)] mod tests { use super::*; @@ -106,4 +118,29 @@ mod tests { 2 ); } + + #[test] + fn test_get_shared_steps() { + assert_eq!( + get_shared_steps(Path::new(""), Path::new("")).collect::<PathBuf>(), + PathBuf::from("") + ); + assert_eq!( + get_shared_steps(Path::new("foo.txt"), Path::new("foo.txt")).collect::<PathBuf>(), + PathBuf::from("foo.txt") + ); + assert_eq!( + get_shared_steps(Path::new("cat/foo.txt"), Path::new("dog/foo.txt")) + .collect::<PathBuf>(), + PathBuf::from("") + ); + assert_eq!( + get_shared_steps( + Path::new("foo/bar/baz/lorem.txt"), + Path::new("foo/bar/ipsum/dolar.txt") + ) + .collect::<PathBuf>(), + PathBuf::from("foo/bar") + ); + } } From ab36a605452bfacc6a571ff14b91aa094dd20579 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Mon, 23 Oct 2023 23:04:05 -0400 Subject: [PATCH 28/47] Generate the minimum relative path by chopping off the shared stem. --- src/blog_post/convert.rs | 46 ++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 63a9fa2..c7d2b46 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -55,14 +55,23 @@ fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>( let containing_file = containing_file.as_ref(); let containing_file_relative_to_output_directory = containing_file.strip_prefix(output_directory)?; + let shared_stem = get_shared_steps( + containing_file_relative_to_output_directory + .parent() + .ok_or("File should exist in a folder.")?, + path_from_web_root + .parent() + .ok_or("File should exist in a folder.")?, + ) + .collect::<PathBuf>(); // Subtracting 1 from the depth to "remove" the file name. - let depth_from_web_root = containing_file_relative_to_output_directory + let depth_from_shared_stem = containing_file_relative_to_output_directory + .strip_prefix(&shared_stem)? .components() .count() - 1; - let prefix = "../".repeat(depth_from_web_root); - // TODO: It should be possible to strip some of the "../" components based on whether the path_from_web_root goes down the same path as the containing file. - let final_path = PathBuf::from(prefix).join(path_from_web_root); + let final_path = PathBuf::from("../".repeat(depth_from_shared_stem)) + .join(path_from_web_root.strip_prefix(shared_stem)?); let final_string = final_path .as_path() .to_str() @@ -83,42 +92,19 @@ fn get_web_path<D: AsRef<Path>, F: AsRef<Path>, P: AsRef<Path>>( } } -#[allow(dead_code)] -fn count_shared_steps<A: AsRef<Path>, B: AsRef<Path>>(left: A, right: B) -> usize { - let left = left.as_ref(); - let right = right.as_ref(); - left.components() - .zip(right.components()) - .position(|(l, r)| l != r) - .unwrap_or_else(|| left.components().count()) -} - -#[allow(dead_code)] fn get_shared_steps<'a>(left: &'a Path, right: &'a Path) -> impl Iterator<Item = Component<'a>> { - let foo: Vec<Component<'_>> = left + let shared_stem = left .components() .zip(right.components()) .take_while(|(l, r)| l == r) - .map(|(l, _r)| l) - .collect(); - foo.into_iter() + .map(|(l, _r)| l); + shared_stem } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_count_shared_steps() { - assert_eq!(count_shared_steps("", ""), 0); - assert_eq!(count_shared_steps("foo.txt", "foo.txt"), 1); - assert_eq!(count_shared_steps("cat/foo.txt", "dog/foo.txt"), 0); - assert_eq!( - count_shared_steps("foo/bar/baz/lorem.txt", "foo/bar/ipsum/dolar.txt"), - 2 - ); - } - #[test] fn test_get_shared_steps() { assert_eq!( From 2413923b3fe8a915f52b535637cbd7aa2d80956e Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Mon, 23 Oct 2023 23:06:14 -0400 Subject: [PATCH 29/47] Render the page title and self link. --- default_environment/templates/html/blog_post_page.dust | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default_environment/templates/html/blog_post_page.dust b/default_environment/templates/html/blog_post_page.dust index eac6770..74bd3e0 100644 --- a/default_environment/templates/html/blog_post_page.dust +++ b/default_environment/templates/html/blog_post_page.dust @@ -1,5 +1,5 @@ <div class="blog_post"> <div class="blog_post_intro"> - + {?.title}{?.self_link}<a class="blog_post_title" href="{.link}">{.title}</a>{:else}<div class="blog_post_title">{.title}</div>{/.self_link}{/.title} </div> </div> From 448e9bb8c62f84f1dbf0f0f03ce413f779659b62 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Mon, 23 Oct 2023 23:36:47 -0400 Subject: [PATCH 30/47] Add comments. --- default_environment/templates/html/blog_post_page.dust | 6 ++++++ default_environment/templates/html/main.dust | 1 + 2 files changed, 7 insertions(+) diff --git a/default_environment/templates/html/blog_post_page.dust b/default_environment/templates/html/blog_post_page.dust index 74bd3e0..6b431c4 100644 --- a/default_environment/templates/html/blog_post_page.dust +++ b/default_environment/templates/html/blog_post_page.dust @@ -1,5 +1,11 @@ <div class="blog_post"> <div class="blog_post_intro"> {?.title}{?.self_link}<a class="blog_post_title" href="{.link}">{.title}</a>{:else}<div class="blog_post_title">{.title}</div>{/.self_link}{/.title} + {! TODO: date? !} + </div> + + {! TODO: Table of contents? !} + + <div class="blog_post_body"> </div> </div> diff --git a/default_environment/templates/html/main.dust b/default_environment/templates/html/main.dust index eba851f..77faec1 100644 --- a/default_environment/templates/html/main.dust +++ b/default_environment/templates/html/main.dust @@ -7,6 +7,7 @@ {?global_settings.page_title}<title>{global_settings.page_title}{/global_settings.page_title} + {! TODO: Header bar with links? !}
{@select key=.type} {@eq value="blog_post_page"}{>blog_post_page/}{/eq} From 3cfcae25a9150d6c99b1ebe168a7a1ff3e934f36 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 23 Oct 2023 23:49:35 -0400 Subject: [PATCH 31/47] Move the render context to its own folder. We are going to have a lot of render context types because there are so many org-mode elements/objects so I'm moving it to a separate folder for organization. --- src/blog_post/convert.rs | 4 ++-- src/blog_post/mod.rs | 1 - src/main.rs | 1 + .../blog_post_page.rs} | 23 +----------------- src/types/global_settings.rs | 24 +++++++++++++++++++ src/types/mod.rs | 5 ++++ 6 files changed, 33 insertions(+), 25 deletions(-) rename src/{blog_post/render_context.rs => types/blog_post_page.rs} (52%) create mode 100644 src/types/global_settings.rs create mode 100644 src/types/mod.rs diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index c7d2b46..81a7410 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -4,9 +4,9 @@ use std::path::PathBuf; use crate::config::Config; use crate::error::CustomError; +use crate::types::GlobalSettings; +use crate::types::RenderBlogPostPage; -use super::render_context::GlobalSettings; -use super::render_context::RenderBlogPostPage; use super::BlogPost; use super::BlogPostPage; diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index bd4e010..2996114 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -1,7 +1,6 @@ mod convert; mod definition; mod page; -mod render_context; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; pub(crate) use page::BlogPostPage; diff --git a/src/main.rs b/src/main.rs index 6e1fb2d..7505bd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod command; mod config; mod error; mod render; +mod types; fn main() -> Result { let rt = tokio::runtime::Runtime::new()?; diff --git a/src/blog_post/render_context.rs b/src/types/blog_post_page.rs similarity index 52% rename from src/blog_post/render_context.rs rename to src/types/blog_post_page.rs index 4085b9d..9ddd0ec 100644 --- a/src/blog_post/render_context.rs +++ b/src/types/blog_post_page.rs @@ -1,27 +1,6 @@ use serde::Serialize; -/// The settings that a "global" to a single dustjs render. -#[derive(Debug, Serialize)] -pub(crate) struct GlobalSettings { - /// The title that goes in the html tag in the <head>. - page_title: Option<String>, - css_files: Vec<String>, - js_files: Vec<String>, -} - -impl GlobalSettings { - pub(crate) fn new( - page_title: Option<String>, - css_files: Vec<String>, - js_files: Vec<String>, - ) -> GlobalSettings { - GlobalSettings { - page_title, - css_files, - js_files, - } - } -} +use super::GlobalSettings; #[derive(Debug, Serialize)] #[serde(tag = "type")] diff --git a/src/types/global_settings.rs b/src/types/global_settings.rs new file mode 100644 index 0000000..53e3eda --- /dev/null +++ b/src/types/global_settings.rs @@ -0,0 +1,24 @@ +use serde::Serialize; + +/// The settings that a "global" to a single dustjs render. +#[derive(Debug, Serialize)] +pub(crate) struct GlobalSettings { + /// The title that goes in the html <title> tag in the <head>. + page_title: Option<String>, + css_files: Vec<String>, + js_files: Vec<String>, +} + +impl GlobalSettings { + pub(crate) fn new( + page_title: Option<String>, + css_files: Vec<String>, + js_files: Vec<String>, + ) -> GlobalSettings { + GlobalSettings { + page_title, + css_files, + js_files, + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..a216de3 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,5 @@ +mod blog_post_page; +mod global_settings; + +pub(crate) use blog_post_page::RenderBlogPostPage; +pub(crate) use global_settings::GlobalSettings; From 77f8375d7a90d25a7ec2ccbf11bc500e7d551cab Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Tue, 24 Oct 2023 00:01:40 -0400 Subject: [PATCH 32/47] Introduce an array of document elements. --- src/blog_post/convert.rs | 7 ++++++- src/types/blog_post_page.rs | 5 +++++ src/types/document_element.rs | 11 +++++++++++ src/types/heading.rs | 6 ++++++ src/types/mod.rs | 6 ++++++ src/types/section.rs | 6 ++++++ 6 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/types/document_element.rs create mode 100644 src/types/heading.rs create mode 100644 src/types/section.rs diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 81a7410..f94db3e 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -39,7 +39,12 @@ pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef< output_file.strip_prefix(output_directory)?, )?; - let ret = RenderBlogPostPage::new(global_settings, page.title.clone(), Some(link_to_blog_post)); + let ret = RenderBlogPostPage::new( + global_settings, + page.title.clone(), + Some(link_to_blog_post), + Vec::new(), + ); Ok(ret) } diff --git a/src/types/blog_post_page.rs b/src/types/blog_post_page.rs index 9ddd0ec..9f607ce 100644 --- a/src/types/blog_post_page.rs +++ b/src/types/blog_post_page.rs @@ -1,6 +1,7 @@ use serde::Serialize; use super::GlobalSettings; +use super::RenderDocumentElement; #[derive(Debug, Serialize)] #[serde(tag = "type")] @@ -12,6 +13,8 @@ pub(crate) struct RenderBlogPostPage { title: Option<String>, self_link: Option<String>, + + children: Vec<RenderDocumentElement>, } impl RenderBlogPostPage { @@ -19,11 +22,13 @@ impl RenderBlogPostPage { global_settings: GlobalSettings, title: Option<String>, self_link: Option<String>, + children: Vec<RenderDocumentElement>, ) -> RenderBlogPostPage { RenderBlogPostPage { global_settings, title, self_link, + children, } } } diff --git a/src/types/document_element.rs b/src/types/document_element.rs new file mode 100644 index 0000000..6b11e0b --- /dev/null +++ b/src/types/document_element.rs @@ -0,0 +1,11 @@ +use serde::Serialize; + +use super::RenderHeading; +use super::RenderSection; + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub(crate) enum RenderDocumentElement { + Heading(RenderHeading), + Section(RenderSection), +} diff --git a/src/types/heading.rs b/src/types/heading.rs new file mode 100644 index 0000000..cda697a --- /dev/null +++ b/src/types/heading.rs @@ -0,0 +1,6 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "heading")] +pub(crate) struct RenderHeading {} diff --git a/src/types/mod.rs b/src/types/mod.rs index a216de3..abede68 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,11 @@ mod blog_post_page; +mod document_element; mod global_settings; +mod heading; +mod section; pub(crate) use blog_post_page::RenderBlogPostPage; +pub(crate) use document_element::RenderDocumentElement; pub(crate) use global_settings::GlobalSettings; +pub(crate) use heading::RenderHeading; +pub(crate) use section::RenderSection; diff --git a/src/types/section.rs b/src/types/section.rs new file mode 100644 index 0000000..d59c81d --- /dev/null +++ b/src/types/section.rs @@ -0,0 +1,6 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "section")] +pub(crate) struct RenderSection {} From 3b472a9e968c19e2fbcc8ffb03df8316a06e36dc Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Tue, 24 Oct 2023 00:04:44 -0400 Subject: [PATCH 33/47] Introduce element and object enums. --- src/types/element.rs | 5 +++++ src/types/mod.rs | 4 ++++ src/types/object.rs | 5 +++++ 3 files changed, 14 insertions(+) create mode 100644 src/types/element.rs create mode 100644 src/types/object.rs diff --git a/src/types/element.rs b/src/types/element.rs new file mode 100644 index 0000000..37c605a --- /dev/null +++ b/src/types/element.rs @@ -0,0 +1,5 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub(crate) enum RenderElement {} diff --git a/src/types/mod.rs b/src/types/mod.rs index abede68..177e2eb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,11 +1,15 @@ mod blog_post_page; mod document_element; +mod element; mod global_settings; mod heading; +mod object; mod section; pub(crate) use blog_post_page::RenderBlogPostPage; pub(crate) use document_element::RenderDocumentElement; +pub(crate) use element::RenderElement; pub(crate) use global_settings::GlobalSettings; pub(crate) use heading::RenderHeading; +pub(crate) use object::RenderObject; pub(crate) use section::RenderSection; diff --git a/src/types/object.rs b/src/types/object.rs new file mode 100644 index 0000000..13e8ee4 --- /dev/null +++ b/src/types/object.rs @@ -0,0 +1,5 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub(crate) enum RenderObject {} From 2b7a19a1d4a310dfc819ddff2d0c110793d8772d Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Tue, 24 Oct 2023 00:36:08 -0400 Subject: [PATCH 34/47] Introduce the corresponding non-render types. --- src/blog_post/convert.rs | 4 ++-- src/blog_post/definition.rs | 3 +++ src/blog_post/document_element.rs | 8 ++++++++ src/blog_post/element.rs | 2 ++ src/blog_post/heading.rs | 10 ++++++++++ src/blog_post/mod.rs | 10 ++++++++++ src/blog_post/object.rs | 2 ++ src/blog_post/page.rs | 15 ++++++++++++++ src/blog_post/section.rs | 10 ++++++++++ src/{types => context}/blog_post_page.rs | 0 src/{types => context}/document_element.rs | 0 src/{types => context}/element.rs | 0 src/{types => context}/global_settings.rs | 0 src/{types => context}/heading.rs | 6 +++++- src/{types => context}/mod.rs | 0 src/{types => context}/object.rs | 0 src/context/section.rs | 23 ++++++++++++++++++++++ src/main.rs | 2 +- src/types/section.rs | 6 ------ 19 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 src/blog_post/document_element.rs create mode 100644 src/blog_post/element.rs create mode 100644 src/blog_post/heading.rs create mode 100644 src/blog_post/object.rs create mode 100644 src/blog_post/section.rs rename src/{types => context}/blog_post_page.rs (100%) rename src/{types => context}/document_element.rs (100%) rename src/{types => context}/element.rs (100%) rename src/{types => context}/global_settings.rs (100%) rename src/{types => context}/heading.rs (52%) rename src/{types => context}/mod.rs (100%) rename src/{types => context}/object.rs (100%) create mode 100644 src/context/section.rs delete mode 100644 src/types/section.rs diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index f94db3e..0c2fb80 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use crate::config::Config; use crate::error::CustomError; -use crate::types::GlobalSettings; -use crate::types::RenderBlogPostPage; +use crate::context::GlobalSettings; +use crate::context::RenderBlogPostPage; use super::BlogPost; use super::BlogPostPage; diff --git a/src/blog_post/definition.rs b/src/blog_post/definition.rs index 96a3c7a..019c422 100644 --- a/src/blog_post/definition.rs +++ b/src/blog_post/definition.rs @@ -7,11 +7,13 @@ use walkdir::WalkDir; use crate::error::CustomError; use super::BlogPostPage; +use super::DocumentElement; #[derive(Debug)] pub(crate) struct BlogPost { pub(crate) id: String, pub(crate) pages: Vec<BlogPostPage>, + pub(crate) children: Vec<DocumentElement>, } impl BlogPost { @@ -57,6 +59,7 @@ impl BlogPost { Ok(BlogPost { id: post_id.to_string_lossy().into_owned(), pages, + children: Vec::new(), }) } inner(root_dir.as_ref(), post_dir.as_ref()).await diff --git a/src/blog_post/document_element.rs b/src/blog_post/document_element.rs new file mode 100644 index 0000000..ab0f28a --- /dev/null +++ b/src/blog_post/document_element.rs @@ -0,0 +1,8 @@ +use super::Heading; +use super::Section; + +#[derive(Debug)] +pub(crate) enum DocumentElement { + Heading(Heading), + Section(Section), +} diff --git a/src/blog_post/element.rs b/src/blog_post/element.rs new file mode 100644 index 0000000..bb64b91 --- /dev/null +++ b/src/blog_post/element.rs @@ -0,0 +1,2 @@ +#[derive(Debug)] +pub(crate) enum Element {} diff --git a/src/blog_post/heading.rs b/src/blog_post/heading.rs new file mode 100644 index 0000000..95a0cee --- /dev/null +++ b/src/blog_post/heading.rs @@ -0,0 +1,10 @@ +use crate::error::CustomError; + +#[derive(Debug)] +pub(crate) struct Heading {} + +impl Heading { + pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<Heading, CustomError> { + todo!() + } +} diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index 2996114..fde7fc4 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -1,6 +1,16 @@ mod convert; mod definition; +mod document_element; +mod element; +mod heading; +mod object; mod page; +mod section; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; +pub(crate) use document_element::DocumentElement; +pub(crate) use element::Element; +pub(crate) use heading::Heading; +pub(crate) use object::Object; pub(crate) use page::BlogPostPage; +pub(crate) use section::Section; diff --git a/src/blog_post/object.rs b/src/blog_post/object.rs new file mode 100644 index 0000000..d28ee34 --- /dev/null +++ b/src/blog_post/object.rs @@ -0,0 +1,2 @@ +#[derive(Debug)] +pub(crate) enum Object {} diff --git a/src/blog_post/page.rs b/src/blog_post/page.rs index 8c56412..7a7aab1 100644 --- a/src/blog_post/page.rs +++ b/src/blog_post/page.rs @@ -2,12 +2,18 @@ use std::path::PathBuf; use crate::error::CustomError; +use super::DocumentElement; +use super::Heading; +use super::Section; + #[derive(Debug)] pub(crate) struct BlogPostPage { /// Relative path from the root of the blog post. pub(crate) path: PathBuf, pub(crate) title: Option<String>, + + pub(crate) children: Vec<DocumentElement>, } impl BlogPostPage { @@ -16,9 +22,18 @@ impl BlogPostPage { document: organic::types::Document<'_>, ) -> Result<BlogPostPage, CustomError> { let path = path.into(); + let mut children = Vec::new(); + if let Some(section) = document.zeroth_section.as_ref() { + children.push(DocumentElement::Section(Section::new(section)?)); + } + for heading in document.children.iter() { + children.push(DocumentElement::Heading(Heading::new(heading)?)); + } + Ok(BlogPostPage { path, title: get_title(&document), + children, }) } diff --git a/src/blog_post/section.rs b/src/blog_post/section.rs new file mode 100644 index 0000000..098dd6c --- /dev/null +++ b/src/blog_post/section.rs @@ -0,0 +1,10 @@ +use crate::error::CustomError; + +#[derive(Debug)] +pub(crate) struct Section {} + +impl Section { + pub(crate) fn new(section: &organic::types::Section<'_>) -> Result<Section, CustomError> { + todo!() + } +} diff --git a/src/types/blog_post_page.rs b/src/context/blog_post_page.rs similarity index 100% rename from src/types/blog_post_page.rs rename to src/context/blog_post_page.rs diff --git a/src/types/document_element.rs b/src/context/document_element.rs similarity index 100% rename from src/types/document_element.rs rename to src/context/document_element.rs diff --git a/src/types/element.rs b/src/context/element.rs similarity index 100% rename from src/types/element.rs rename to src/context/element.rs diff --git a/src/types/global_settings.rs b/src/context/global_settings.rs similarity index 100% rename from src/types/global_settings.rs rename to src/context/global_settings.rs diff --git a/src/types/heading.rs b/src/context/heading.rs similarity index 52% rename from src/types/heading.rs rename to src/context/heading.rs index cda697a..2a30e7a 100644 --- a/src/types/heading.rs +++ b/src/context/heading.rs @@ -1,6 +1,10 @@ use serde::Serialize; +use super::RenderObject; + #[derive(Debug, Serialize)] #[serde(tag = "type")] #[serde(rename = "heading")] -pub(crate) struct RenderHeading {} +pub(crate) struct RenderHeading { + title: Vec<RenderObject>, +} diff --git a/src/types/mod.rs b/src/context/mod.rs similarity index 100% rename from src/types/mod.rs rename to src/context/mod.rs diff --git a/src/types/object.rs b/src/context/object.rs similarity index 100% rename from src/types/object.rs rename to src/context/object.rs diff --git a/src/context/section.rs b/src/context/section.rs new file mode 100644 index 0000000..12d0a71 --- /dev/null +++ b/src/context/section.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use serde::Serialize; + +use crate::blog_post::Section; +use crate::config::Config; +use crate::error::CustomError; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "section")] +pub(crate) struct RenderSection {} + +impl RenderSection { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + section: &Section, + ) -> Result<RenderSection, CustomError> { + todo!() + } +} diff --git a/src/main.rs b/src/main.rs index 7505bd4..e280cc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ mod command; mod config; mod error; mod render; -mod types; +mod context; fn main() -> Result<ExitCode, CustomError> { let rt = tokio::runtime::Runtime::new()?; diff --git a/src/types/section.rs b/src/types/section.rs deleted file mode 100644 index d59c81d..0000000 --- a/src/types/section.rs +++ /dev/null @@ -1,6 +0,0 @@ -use serde::Serialize; - -#[derive(Debug, Serialize)] -#[serde(tag = "type")] -#[serde(rename = "section")] -pub(crate) struct RenderSection {} From 31a3efe4176081f23577484c52d95bffab9fcb3c Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Tue, 24 Oct 2023 00:51:28 -0400 Subject: [PATCH 35/47] Only print the contexts. This allows us to pipe the output to jq to see the context easier. We can see the rendered output in the files written to disk. --- src/blog_post/convert.rs | 2 +- src/blog_post/heading.rs | 8 ++++++-- src/blog_post/section.rs | 2 +- src/command/build/render.rs | 1 - src/context/section.rs | 2 +- src/render/duster_renderer.rs | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 0c2fb80..293b491 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -3,9 +3,9 @@ use std::path::Path; use std::path::PathBuf; use crate::config::Config; -use crate::error::CustomError; use crate::context::GlobalSettings; use crate::context::RenderBlogPostPage; +use crate::error::CustomError; use super::BlogPost; use super::BlogPostPage; diff --git a/src/blog_post/heading.rs b/src/blog_post/heading.rs index 95a0cee..d889bf2 100644 --- a/src/blog_post/heading.rs +++ b/src/blog_post/heading.rs @@ -1,10 +1,14 @@ use crate::error::CustomError; +use super::Object; + #[derive(Debug)] -pub(crate) struct Heading {} +pub(crate) struct Heading { + title: Vec<Object>, +} impl Heading { pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<Heading, CustomError> { - todo!() + Ok(Heading { title: Vec::new() }) } } diff --git a/src/blog_post/section.rs b/src/blog_post/section.rs index 098dd6c..b7b888d 100644 --- a/src/blog_post/section.rs +++ b/src/blog_post/section.rs @@ -5,6 +5,6 @@ pub(crate) struct Section {} impl Section { pub(crate) fn new(section: &organic::types::Section<'_>) -> Result<Section, CustomError> { - todo!() + Ok(Section {}) } } diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 808d5b3..04a73f6 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -72,7 +72,6 @@ impl SiteRenderer { blog_post_page, )?; let rendered_output = renderer_integration.render(render_context)?; - println!("Rendered: {}", rendered_output); let parent_directory = output_path .parent() .ok_or("Output file should have a containing directory.")?; diff --git a/src/context/section.rs b/src/context/section.rs index 12d0a71..ff11345 100644 --- a/src/context/section.rs +++ b/src/context/section.rs @@ -18,6 +18,6 @@ impl RenderSection { output_file: F, section: &Section, ) -> Result<RenderSection, CustomError> { - todo!() + Ok(RenderSection {}) } } diff --git a/src/render/duster_renderer.rs b/src/render/duster_renderer.rs index 5602731..e5078a9 100644 --- a/src/render/duster_renderer.rs +++ b/src/render/duster_renderer.rs @@ -34,7 +34,7 @@ impl<'a> RendererIntegration<'a> for DusterRenderer<'a> { } // 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); + println!("{}", 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) From ba2756c7625b102dc23c971dde4bda86239bfea2 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 10:23:05 -0400 Subject: [PATCH 36/47] Create intermediate representation for plain text. --- src/blog_post/heading.rs | 7 +++++- src/blog_post/mod.rs | 2 ++ src/blog_post/object.rs | 44 +++++++++++++++++++++++++++++++++- src/blog_post/plain_text.rs | 17 +++++++++++++ src/blog_post/util.rs | 48 +++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/blog_post/plain_text.rs create mode 100644 src/blog_post/util.rs diff --git a/src/blog_post/heading.rs b/src/blog_post/heading.rs index d889bf2..109c65a 100644 --- a/src/blog_post/heading.rs +++ b/src/blog_post/heading.rs @@ -9,6 +9,11 @@ pub(crate) struct Heading { impl Heading { pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<Heading, CustomError> { - Ok(Heading { title: Vec::new() }) + let title = heading + .title + .iter() + .map(Object::new) + .collect::<Result<Vec<_>, _>>()?; + Ok(Heading { title }) } } diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index fde7fc4..b8ff674 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -5,7 +5,9 @@ mod element; mod heading; mod object; mod page; +mod plain_text; mod section; +mod util; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; pub(crate) use document_element::DocumentElement; diff --git a/src/blog_post/object.rs b/src/blog_post/object.rs index d28ee34..b758ac3 100644 --- a/src/blog_post/object.rs +++ b/src/blog_post/object.rs @@ -1,2 +1,44 @@ +use crate::error::CustomError; + +use super::plain_text::PlainText; + #[derive(Debug)] -pub(crate) enum Object {} +pub(crate) enum Object { + PlainText(PlainText), +} + +impl Object { + pub(crate) fn new(obj: &organic::types::Object<'_>) -> Result<Object, 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(Object::PlainText(PlainText::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!(), + } + } +} diff --git a/src/blog_post/plain_text.rs b/src/blog_post/plain_text.rs new file mode 100644 index 0000000..e62191f --- /dev/null +++ b/src/blog_post/plain_text.rs @@ -0,0 +1,17 @@ +use crate::blog_post::util::coalesce_whitespace; +use crate::error::CustomError; + +#[derive(Debug)] +pub(crate) struct PlainText { + source: String, +} + +impl PlainText { + pub(crate) fn new( + plain_text: &organic::types::PlainText<'_>, + ) -> Result<PlainText, CustomError> { + Ok(PlainText { + source: coalesce_whitespace(plain_text.source).into_owned(), + }) + } +} diff --git a/src/blog_post/util.rs b/src/blog_post/util.rs new file mode 100644 index 0000000..b482b98 --- /dev/null +++ b/src/blog_post/util.rs @@ -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 }, +} From 4c59011389c5d03c20d0325ead7f3ebc8c662431 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 12:14:07 -0400 Subject: [PATCH 37/47] Copy heading level. --- src/blog_post/heading.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blog_post/heading.rs b/src/blog_post/heading.rs index 109c65a..f27bcc8 100644 --- a/src/blog_post/heading.rs +++ b/src/blog_post/heading.rs @@ -5,6 +5,7 @@ use super::Object; #[derive(Debug)] pub(crate) struct Heading { title: Vec<Object>, + level: organic::types::HeadlineLevel, } impl Heading { @@ -14,6 +15,9 @@ impl Heading { .iter() .map(Object::new) .collect::<Result<Vec<_>, _>>()?; - Ok(Heading { title }) + Ok(Heading { + title, + level: heading.level, + }) } } From 744d3e50fb9104a32118af557f9d22e2d9629fa3 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 12:47:12 -0400 Subject: [PATCH 38/47] Convert intermediate objects into render objects. --- src/blog_post/convert.rs | 33 ++++++++++++++++++++++++++++++++- src/blog_post/heading.rs | 4 ++-- src/context/heading.rs | 26 ++++++++++++++++++++++++++ src/context/object.rs | 17 +++++++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/blog_post/convert.rs b/src/blog_post/convert.rs index 293b491..cd321cd 100644 --- a/src/blog_post/convert.rs +++ b/src/blog_post/convert.rs @@ -5,10 +5,14 @@ use std::path::PathBuf; use crate::config::Config; use crate::context::GlobalSettings; use crate::context::RenderBlogPostPage; +use crate::context::RenderDocumentElement; +use crate::context::RenderHeading; +use crate::context::RenderSection; use crate::error::CustomError; use super::BlogPost; use super::BlogPostPage; +use super::DocumentElement; pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef<Path>>( 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)?, )?; + let children = { + let mut children = Vec::new(); + + for child in page.children.iter() { + match child { + DocumentElement::Heading(heading) => { + children.push(RenderDocumentElement::Heading(RenderHeading::new( + config, + output_directory, + output_file, + heading, + )?)); + } + DocumentElement::Section(section) => { + children.push(RenderDocumentElement::Section(RenderSection::new( + config, + output_directory, + output_file, + section, + )?)); + } + } + } + + children + }; + let ret = RenderBlogPostPage::new( global_settings, page.title.clone(), Some(link_to_blog_post), - Vec::new(), + children, ); Ok(ret) } diff --git a/src/blog_post/heading.rs b/src/blog_post/heading.rs index f27bcc8..57a5da8 100644 --- a/src/blog_post/heading.rs +++ b/src/blog_post/heading.rs @@ -4,8 +4,8 @@ use super::Object; #[derive(Debug)] pub(crate) struct Heading { - title: Vec<Object>, - level: organic::types::HeadlineLevel, + pub(crate) level: organic::types::HeadlineLevel, + pub(crate) title: Vec<Object>, } impl Heading { diff --git a/src/context/heading.rs b/src/context/heading.rs index 2a30e7a..1192e78 100644 --- a/src/context/heading.rs +++ b/src/context/heading.rs @@ -1,10 +1,36 @@ +use std::path::Path; + use serde::Serialize; +use crate::blog_post::Heading; +use crate::config::Config; +use crate::error::CustomError; + use super::RenderObject; #[derive(Debug, Serialize)] #[serde(tag = "type")] #[serde(rename = "heading")] pub(crate) struct RenderHeading { + level: organic::types::HeadlineLevel, title: Vec<RenderObject>, } + +impl RenderHeading { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + heading: &Heading, + ) -> 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, + }) + } +} diff --git a/src/context/object.rs b/src/context/object.rs index 13e8ee4..724c5d1 100644 --- a/src/context/object.rs +++ b/src/context/object.rs @@ -1,5 +1,22 @@ +use std::path::Path; + use serde::Serialize; +use crate::blog_post::Object; +use crate::config::Config; +use crate::error::CustomError; + #[derive(Debug, Serialize)] #[serde(untagged)] pub(crate) enum RenderObject {} + +impl RenderObject { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + section: &Object, + ) -> Result<RenderObject, CustomError> { + todo!() + } +} From 1ac39c2a6fbc39303dff21499ec747db7a4b64c4 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 13:01:45 -0400 Subject: [PATCH 39/47] Add RenderPlainText. --- src/blog_post/mod.rs | 1 + src/context/mod.rs | 1 + src/context/object.rs | 17 ++++++++++++++--- src/context/plain_text.rs | 23 +++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/context/plain_text.rs diff --git a/src/blog_post/mod.rs b/src/blog_post/mod.rs index b8ff674..fae23ec 100644 --- a/src/blog_post/mod.rs +++ b/src/blog_post/mod.rs @@ -15,4 +15,5 @@ pub(crate) use element::Element; pub(crate) use heading::Heading; pub(crate) use object::Object; pub(crate) use page::BlogPostPage; +pub(crate) use plain_text::PlainText; pub(crate) use section::Section; diff --git a/src/context/mod.rs b/src/context/mod.rs index 177e2eb..bbfa0fc 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -4,6 +4,7 @@ mod element; mod global_settings; mod heading; mod object; +mod plain_text; mod section; pub(crate) use blog_post_page::RenderBlogPostPage; diff --git a/src/context/object.rs b/src/context/object.rs index 724c5d1..558f1d2 100644 --- a/src/context/object.rs +++ b/src/context/object.rs @@ -6,17 +6,28 @@ use crate::blog_post::Object; use crate::config::Config; use crate::error::CustomError; +use super::plain_text::RenderPlainText; + #[derive(Debug, Serialize)] #[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, - section: &Object, + object: &Object, ) -> Result<RenderObject, CustomError> { - todo!() + match object { + Object::PlainText(inner) => Ok(RenderObject::PlainText(RenderPlainText::new( + config, + output_directory, + output_file, + inner, + )?)), + } } } diff --git a/src/context/plain_text.rs b/src/context/plain_text.rs new file mode 100644 index 0000000..aae8012 --- /dev/null +++ b/src/context/plain_text.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use serde::Serialize; + +use crate::blog_post::PlainText; +use crate::config::Config; +use crate::error::CustomError; + +#[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: &PlainText, + ) -> Result<RenderPlainText, CustomError> { + Ok(RenderPlainText {}) + } +} From e3b5f7f74f8c5b53ed369f46416067add6b70a6b Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 13:05:34 -0400 Subject: [PATCH 40/47] 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. --- src/blog_post/document_element.rs | 8 -------- src/blog_post/element.rs | 2 -- src/command/build/render.rs | 4 ++-- src/command/build/runner.rs | 2 +- src/context/heading.rs | 4 ++-- src/context/object.rs | 6 +++--- src/context/plain_text.rs | 4 ++-- src/context/section.rs | 4 ++-- src/{blog_post => intermediate}/convert.rs | 6 +++--- src/{blog_post => intermediate}/definition.rs | 4 ++-- src/intermediate/document_element.rs | 8 ++++++++ src/intermediate/element.rs | 2 ++ src/{blog_post => intermediate}/heading.rs | 14 +++++++------- src/{blog_post => intermediate}/mod.rs | 12 ++++++------ src/{blog_post => intermediate}/object.rs | 12 ++++++------ src/{blog_post => intermediate}/page.rs | 12 ++++++------ src/{blog_post => intermediate}/plain_text.rs | 10 +++++----- src/{blog_post => intermediate}/section.rs | 8 ++++---- src/{blog_post => intermediate}/util.rs | 0 src/main.rs | 6 +++--- 20 files changed, 64 insertions(+), 64 deletions(-) delete mode 100644 src/blog_post/document_element.rs delete mode 100644 src/blog_post/element.rs rename src/{blog_post => intermediate}/convert.rs (97%) rename src/{blog_post => intermediate}/definition.rs (97%) create mode 100644 src/intermediate/document_element.rs create mode 100644 src/intermediate/element.rs rename src/{blog_post => intermediate}/heading.rs (65%) rename src/{blog_post => intermediate}/mod.rs (55%) rename src/{blog_post => intermediate}/object.rs (90%) rename src/{blog_post => intermediate}/page.rs (83%) rename src/{blog_post => intermediate}/plain_text.rs (58%) rename src/{blog_post => intermediate}/section.rs (54%) rename src/{blog_post => intermediate}/util.rs (100%) diff --git a/src/blog_post/document_element.rs b/src/blog_post/document_element.rs deleted file mode 100644 index ab0f28a..0000000 --- a/src/blog_post/document_element.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::Heading; -use super::Section; - -#[derive(Debug)] -pub(crate) enum DocumentElement { - Heading(Heading), - Section(Section), -} diff --git a/src/blog_post/element.rs b/src/blog_post/element.rs deleted file mode 100644 index bb64b91..0000000 --- a/src/blog_post/element.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[derive(Debug)] -pub(crate) enum Element {} diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 04a73f6..853028a 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -4,10 +4,10 @@ use std::path::PathBuf; use include_dir::include_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::error::CustomError; +use crate::intermediate::convert_blog_post_page_to_render_context; +use crate::intermediate::BlogPost; use crate::render::DusterRenderer; use crate::render::RendererIntegration; diff --git a/src/command/build/runner.rs b/src/command/build/runner.rs index bb267f6..13dc846 100644 --- a/src/command/build/runner.rs +++ b/src/command/build/runner.rs @@ -1,10 +1,10 @@ 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; +use crate::intermediate::BlogPost; pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> { let config = Config::load_from_file(args.config).await?; diff --git a/src/context/heading.rs b/src/context/heading.rs index 1192e78..c609c5a 100644 --- a/src/context/heading.rs +++ b/src/context/heading.rs @@ -2,9 +2,9 @@ use std::path::Path; use serde::Serialize; -use crate::blog_post::Heading; use crate::config::Config; use crate::error::CustomError; +use crate::intermediate::IHeading; use super::RenderObject; @@ -21,7 +21,7 @@ impl RenderHeading { config: &Config, output_directory: D, output_file: F, - heading: &Heading, + heading: &IHeading, ) -> Result<RenderHeading, CustomError> { let title = heading .title diff --git a/src/context/object.rs b/src/context/object.rs index 558f1d2..8cccc88 100644 --- a/src/context/object.rs +++ b/src/context/object.rs @@ -2,9 +2,9 @@ use std::path::Path; use serde::Serialize; -use crate::blog_post::Object; use crate::config::Config; use crate::error::CustomError; +use crate::intermediate::IObject; use super::plain_text::RenderPlainText; @@ -19,10 +19,10 @@ impl RenderObject { config: &Config, output_directory: D, output_file: F, - object: &Object, + object: &IObject, ) -> Result<RenderObject, CustomError> { match object { - Object::PlainText(inner) => Ok(RenderObject::PlainText(RenderPlainText::new( + IObject::PlainText(inner) => Ok(RenderObject::PlainText(RenderPlainText::new( config, output_directory, output_file, diff --git a/src/context/plain_text.rs b/src/context/plain_text.rs index aae8012..24bf142 100644 --- a/src/context/plain_text.rs +++ b/src/context/plain_text.rs @@ -2,9 +2,9 @@ use std::path::Path; use serde::Serialize; -use crate::blog_post::PlainText; use crate::config::Config; use crate::error::CustomError; +use crate::intermediate::IPlainText; #[derive(Debug, Serialize)] #[serde(tag = "type")] @@ -16,7 +16,7 @@ impl RenderPlainText { config: &Config, output_directory: D, output_file: F, - heading: &PlainText, + heading: &IPlainText, ) -> Result<RenderPlainText, CustomError> { Ok(RenderPlainText {}) } diff --git a/src/context/section.rs b/src/context/section.rs index ff11345..937422a 100644 --- a/src/context/section.rs +++ b/src/context/section.rs @@ -2,9 +2,9 @@ use std::path::Path; use serde::Serialize; -use crate::blog_post::Section; use crate::config::Config; use crate::error::CustomError; +use crate::intermediate::ISection; #[derive(Debug, Serialize)] #[serde(tag = "type")] @@ -16,7 +16,7 @@ impl RenderSection { config: &Config, output_directory: D, output_file: F, - section: &Section, + section: &ISection, ) -> Result<RenderSection, CustomError> { Ok(RenderSection {}) } diff --git a/src/blog_post/convert.rs b/src/intermediate/convert.rs similarity index 97% rename from src/blog_post/convert.rs rename to src/intermediate/convert.rs index cd321cd..f28ac59 100644 --- a/src/blog_post/convert.rs +++ b/src/intermediate/convert.rs @@ -12,7 +12,7 @@ use crate::error::CustomError; use super::BlogPost; use super::BlogPostPage; -use super::DocumentElement; +use super::IDocumentElement; pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef<Path>>( config: &Config, @@ -48,7 +48,7 @@ pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef< for child in page.children.iter() { match child { - DocumentElement::Heading(heading) => { + IDocumentElement::Heading(heading) => { children.push(RenderDocumentElement::Heading(RenderHeading::new( config, output_directory, @@ -56,7 +56,7 @@ pub(crate) fn convert_blog_post_page_to_render_context<D: AsRef<Path>, F: AsRef< heading, )?)); } - DocumentElement::Section(section) => { + IDocumentElement::Section(section) => { children.push(RenderDocumentElement::Section(RenderSection::new( config, output_directory, diff --git a/src/blog_post/definition.rs b/src/intermediate/definition.rs similarity index 97% rename from src/blog_post/definition.rs rename to src/intermediate/definition.rs index 019c422..c81053a 100644 --- a/src/blog_post/definition.rs +++ b/src/intermediate/definition.rs @@ -7,13 +7,13 @@ use walkdir::WalkDir; use crate::error::CustomError; use super::BlogPostPage; -use super::DocumentElement; +use super::IDocumentElement; #[derive(Debug)] pub(crate) struct BlogPost { pub(crate) id: String, pub(crate) pages: Vec<BlogPostPage>, - pub(crate) children: Vec<DocumentElement>, + pub(crate) children: Vec<IDocumentElement>, } impl BlogPost { diff --git a/src/intermediate/document_element.rs b/src/intermediate/document_element.rs new file mode 100644 index 0000000..3f35270 --- /dev/null +++ b/src/intermediate/document_element.rs @@ -0,0 +1,8 @@ +use super::IHeading; +use super::ISection; + +#[derive(Debug)] +pub(crate) enum IDocumentElement { + Heading(IHeading), + Section(ISection), +} diff --git a/src/intermediate/element.rs b/src/intermediate/element.rs new file mode 100644 index 0000000..3aeafe0 --- /dev/null +++ b/src/intermediate/element.rs @@ -0,0 +1,2 @@ +#[derive(Debug)] +pub(crate) enum IElement {} diff --git a/src/blog_post/heading.rs b/src/intermediate/heading.rs similarity index 65% rename from src/blog_post/heading.rs rename to src/intermediate/heading.rs index 57a5da8..c8bb925 100644 --- a/src/blog_post/heading.rs +++ b/src/intermediate/heading.rs @@ -1,21 +1,21 @@ use crate::error::CustomError; -use super::Object; +use super::IObject; #[derive(Debug)] -pub(crate) struct Heading { +pub(crate) struct IHeading { pub(crate) level: organic::types::HeadlineLevel, - pub(crate) title: Vec<Object>, + pub(crate) title: Vec<IObject>, } -impl Heading { - pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<Heading, CustomError> { +impl IHeading { + pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<IHeading, CustomError> { let title = heading .title .iter() - .map(Object::new) + .map(IObject::new) .collect::<Result<Vec<_>, _>>()?; - Ok(Heading { + Ok(IHeading { title, level: heading.level, }) diff --git a/src/blog_post/mod.rs b/src/intermediate/mod.rs similarity index 55% rename from src/blog_post/mod.rs rename to src/intermediate/mod.rs index fae23ec..b8760ff 100644 --- a/src/blog_post/mod.rs +++ b/src/intermediate/mod.rs @@ -10,10 +10,10 @@ mod section; mod util; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; -pub(crate) use document_element::DocumentElement; -pub(crate) use element::Element; -pub(crate) use heading::Heading; -pub(crate) use object::Object; +pub(crate) use document_element::IDocumentElement; +pub(crate) use element::IElement; +pub(crate) use heading::IHeading; +pub(crate) use object::IObject; pub(crate) use page::BlogPostPage; -pub(crate) use plain_text::PlainText; -pub(crate) use section::Section; +pub(crate) use plain_text::IPlainText; +pub(crate) use section::ISection; diff --git a/src/blog_post/object.rs b/src/intermediate/object.rs similarity index 90% rename from src/blog_post/object.rs rename to src/intermediate/object.rs index b758ac3..f15e90f 100644 --- a/src/blog_post/object.rs +++ b/src/intermediate/object.rs @@ -1,14 +1,14 @@ use crate::error::CustomError; -use super::plain_text::PlainText; +use super::plain_text::IPlainText; #[derive(Debug)] -pub(crate) enum Object { - PlainText(PlainText), +pub(crate) enum IObject { + PlainText(IPlainText), } -impl Object { - pub(crate) fn new(obj: &organic::types::Object<'_>) -> Result<Object, CustomError> { +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!(), @@ -17,7 +17,7 @@ impl Object { organic::types::Object::Code(_) => todo!(), organic::types::Object::Verbatim(_) => todo!(), organic::types::Object::PlainText(plain_text) => { - Ok(Object::PlainText(PlainText::new(plain_text)?)) + Ok(IObject::PlainText(IPlainText::new(plain_text)?)) } organic::types::Object::RegularLink(_) => todo!(), organic::types::Object::RadioLink(_) => todo!(), diff --git a/src/blog_post/page.rs b/src/intermediate/page.rs similarity index 83% rename from src/blog_post/page.rs rename to src/intermediate/page.rs index 7a7aab1..fde0ce8 100644 --- a/src/blog_post/page.rs +++ b/src/intermediate/page.rs @@ -2,9 +2,9 @@ use std::path::PathBuf; use crate::error::CustomError; -use super::DocumentElement; -use super::Heading; -use super::Section; +use super::IDocumentElement; +use super::IHeading; +use super::ISection; #[derive(Debug)] pub(crate) struct BlogPostPage { @@ -13,7 +13,7 @@ pub(crate) struct BlogPostPage { pub(crate) title: Option<String>, - pub(crate) children: Vec<DocumentElement>, + pub(crate) children: Vec<IDocumentElement>, } impl BlogPostPage { @@ -24,10 +24,10 @@ impl BlogPostPage { let path = path.into(); let mut children = Vec::new(); 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() { - children.push(DocumentElement::Heading(Heading::new(heading)?)); + children.push(IDocumentElement::Heading(IHeading::new(heading)?)); } Ok(BlogPostPage { diff --git a/src/blog_post/plain_text.rs b/src/intermediate/plain_text.rs similarity index 58% rename from src/blog_post/plain_text.rs rename to src/intermediate/plain_text.rs index e62191f..24cebb2 100644 --- a/src/blog_post/plain_text.rs +++ b/src/intermediate/plain_text.rs @@ -1,16 +1,16 @@ -use crate::blog_post::util::coalesce_whitespace; use crate::error::CustomError; +use crate::intermediate::util::coalesce_whitespace; #[derive(Debug)] -pub(crate) struct PlainText { +pub(crate) struct IPlainText { source: String, } -impl PlainText { +impl IPlainText { pub(crate) fn new( plain_text: &organic::types::PlainText<'_>, - ) -> Result<PlainText, CustomError> { - Ok(PlainText { + ) -> Result<IPlainText, CustomError> { + Ok(IPlainText { source: coalesce_whitespace(plain_text.source).into_owned(), }) } diff --git a/src/blog_post/section.rs b/src/intermediate/section.rs similarity index 54% rename from src/blog_post/section.rs rename to src/intermediate/section.rs index b7b888d..d7b0069 100644 --- a/src/blog_post/section.rs +++ b/src/intermediate/section.rs @@ -1,10 +1,10 @@ use crate::error::CustomError; #[derive(Debug)] -pub(crate) struct Section {} +pub(crate) struct ISection {} -impl Section { - pub(crate) fn new(section: &organic::types::Section<'_>) -> Result<Section, CustomError> { - Ok(Section {}) +impl ISection { + pub(crate) fn new(section: &organic::types::Section<'_>) -> Result<ISection, CustomError> { + Ok(ISection {}) } } diff --git a/src/blog_post/util.rs b/src/intermediate/util.rs similarity index 100% rename from src/blog_post/util.rs rename to src/intermediate/util.rs diff --git a/src/main.rs b/src/main.rs index e280cc7..d2d3edd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,13 @@ use self::cli::parameters::Commands; use self::command::build::build_site; use self::command::init::init_writer_folder; use self::error::CustomError; -mod blog_post; mod cli; mod command; mod config; -mod error; -mod render; mod context; +mod error; +mod intermediate; +mod render; fn main() -> Result<ExitCode, CustomError> { let rt = tokio::runtime::Runtime::new()?; From c6cf5f75accb471832bf32da5a670b336d7083db Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 14:43:06 -0400 Subject: [PATCH 41/47] Introduce a registry into the conversion to intermediate format. --- src/intermediate/definition.rs | 21 +++++++++++++++++---- src/intermediate/heading.rs | 8 ++++++-- src/intermediate/mod.rs | 1 + src/intermediate/object.rs | 8 ++++++-- src/intermediate/page.rs | 8 +++++--- src/intermediate/plain_text.rs | 3 +++ src/intermediate/registry.rs | 24 ++++++++++++++++++++++++ src/intermediate/section.rs | 7 ++++++- 8 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 src/intermediate/registry.rs diff --git a/src/intermediate/definition.rs b/src/intermediate/definition.rs index c81053a..897b49a 100644 --- a/src/intermediate/definition.rs +++ b/src/intermediate/definition.rs @@ -5,15 +5,14 @@ use tokio::task::JoinHandle; use walkdir::WalkDir; use crate::error::CustomError; +use crate::intermediate::registry::Registry; use super::BlogPostPage; -use super::IDocumentElement; #[derive(Debug)] pub(crate) struct BlogPost { pub(crate) id: String, pub(crate) pages: Vec<BlogPostPage>, - pub(crate) children: Vec<IDocumentElement>, } impl BlogPost { @@ -44,12 +43,27 @@ impl BlogPost { ret }; + let mut registry = Registry::new(); + + // Assign IDs to the targets + for (_real_path, _contents, parsed_document) in parsed_org_files.iter() { + organic::types::AstNode::from(parsed_document) + .iter_all_ast_nodes() + .for_each(|node| match node { + organic::types::AstNode::Target(target) => { + registry.get_target(target.value); + } + _ => {} + }); + } + let pages = { let mut ret = Vec::new(); - for (real_path, _contents, parsed_document) in parsed_org_files { + for (real_path, _contents, parsed_document) in parsed_org_files.iter() { let relative_to_post_dir_path = real_path.strip_prefix(post_dir)?; ret.push(BlogPostPage::new( relative_to_post_dir_path, + &mut registry, parsed_document, )?); } @@ -59,7 +73,6 @@ impl BlogPost { Ok(BlogPost { id: post_id.to_string_lossy().into_owned(), pages, - children: Vec::new(), }) } inner(root_dir.as_ref(), post_dir.as_ref()).await diff --git a/src/intermediate/heading.rs b/src/intermediate/heading.rs index c8bb925..331b0b8 100644 --- a/src/intermediate/heading.rs +++ b/src/intermediate/heading.rs @@ -1,5 +1,6 @@ use crate::error::CustomError; +use super::registry::Registry; use super::IObject; #[derive(Debug)] @@ -9,11 +10,14 @@ pub(crate) struct IHeading { } impl IHeading { - pub(crate) fn new(heading: &organic::types::Heading<'_>) -> Result<IHeading, CustomError> { + pub(crate) fn new( + registry: &mut Registry<'_>, + heading: &organic::types::Heading<'_>, + ) -> Result<IHeading, CustomError> { let title = heading .title .iter() - .map(IObject::new) + .map(|obj| IObject::new(registry, obj)) .collect::<Result<Vec<_>, _>>()?; Ok(IHeading { title, diff --git a/src/intermediate/mod.rs b/src/intermediate/mod.rs index b8760ff..75aab28 100644 --- a/src/intermediate/mod.rs +++ b/src/intermediate/mod.rs @@ -6,6 +6,7 @@ mod heading; mod object; mod page; mod plain_text; +mod registry; mod section; mod util; pub(crate) use convert::convert_blog_post_page_to_render_context; diff --git a/src/intermediate/object.rs b/src/intermediate/object.rs index f15e90f..87864f4 100644 --- a/src/intermediate/object.rs +++ b/src/intermediate/object.rs @@ -1,6 +1,7 @@ use crate::error::CustomError; use super::plain_text::IPlainText; +use super::registry::Registry; #[derive(Debug)] pub(crate) enum IObject { @@ -8,7 +9,10 @@ pub(crate) enum IObject { } impl IObject { - pub(crate) fn new(obj: &organic::types::Object<'_>) -> Result<IObject, CustomError> { + pub(crate) fn new( + registry: &mut Registry<'_>, + obj: &organic::types::Object<'_>, + ) -> Result<IObject, CustomError> { match obj { organic::types::Object::Bold(_) => todo!(), organic::types::Object::Italic(_) => todo!(), @@ -17,7 +21,7 @@ impl IObject { organic::types::Object::Code(_) => todo!(), organic::types::Object::Verbatim(_) => todo!(), organic::types::Object::PlainText(plain_text) => { - Ok(IObject::PlainText(IPlainText::new(plain_text)?)) + Ok(IObject::PlainText(IPlainText::new(registry, plain_text)?)) } organic::types::Object::RegularLink(_) => todo!(), organic::types::Object::RadioLink(_) => todo!(), diff --git a/src/intermediate/page.rs b/src/intermediate/page.rs index fde0ce8..17d7ac0 100644 --- a/src/intermediate/page.rs +++ b/src/intermediate/page.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use crate::error::CustomError; +use super::registry::Registry; use super::IDocumentElement; use super::IHeading; use super::ISection; @@ -19,15 +20,16 @@ pub(crate) struct BlogPostPage { impl BlogPostPage { pub(crate) fn new<P: Into<PathBuf>>( path: P, - document: organic::types::Document<'_>, + registry: &mut Registry<'_>, + document: &organic::types::Document<'_>, ) -> Result<BlogPostPage, CustomError> { let path = path.into(); let mut children = Vec::new(); if let Some(section) = document.zeroth_section.as_ref() { - children.push(IDocumentElement::Section(ISection::new(section)?)); + children.push(IDocumentElement::Section(ISection::new(registry, section)?)); } for heading in document.children.iter() { - children.push(IDocumentElement::Heading(IHeading::new(heading)?)); + children.push(IDocumentElement::Heading(IHeading::new(registry, heading)?)); } Ok(BlogPostPage { diff --git a/src/intermediate/plain_text.rs b/src/intermediate/plain_text.rs index 24cebb2..f794680 100644 --- a/src/intermediate/plain_text.rs +++ b/src/intermediate/plain_text.rs @@ -1,6 +1,8 @@ use crate::error::CustomError; use crate::intermediate::util::coalesce_whitespace; +use super::registry::Registry; + #[derive(Debug)] pub(crate) struct IPlainText { source: String, @@ -8,6 +10,7 @@ pub(crate) struct IPlainText { impl IPlainText { pub(crate) fn new( + registry: &mut Registry<'_>, plain_text: &organic::types::PlainText<'_>, ) -> Result<IPlainText, CustomError> { Ok(IPlainText { diff --git a/src/intermediate/registry.rs b/src/intermediate/registry.rs new file mode 100644 index 0000000..d74e6d9 --- /dev/null +++ b/src/intermediate/registry.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; + +type IdCounter = u16; + +pub(crate) struct Registry<'p> { + id_counter: IdCounter, + targets: HashMap<&'p str, String>, +} + +impl<'p> Registry<'p> { + pub(crate) fn new() -> Registry<'p> { + Registry { + id_counter: 0, + targets: HashMap::new(), + } + } + + pub(crate) fn get_target<'b>(&'b mut self, body: &'p str) -> &'b String { + self.targets.entry(body).or_insert_with(|| { + self.id_counter += 1; + format!("target_{}", self.id_counter) + }) + } +} diff --git a/src/intermediate/section.rs b/src/intermediate/section.rs index d7b0069..2afd831 100644 --- a/src/intermediate/section.rs +++ b/src/intermediate/section.rs @@ -1,10 +1,15 @@ use crate::error::CustomError; +use super::registry::Registry; + #[derive(Debug)] pub(crate) struct ISection {} impl ISection { - pub(crate) fn new(section: &organic::types::Section<'_>) -> Result<ISection, CustomError> { + pub(crate) fn new( + registry: &mut Registry<'_>, + section: &organic::types::Section<'_>, + ) -> Result<ISection, CustomError> { Ok(ISection {}) } } From 7b012302342669dff430d50b9243d14aede2a8c6 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 14:54:54 -0400 Subject: [PATCH 42/47] Add target. --- src/context/mod.rs | 1 + src/context/object.rs | 8 ++++++++ src/context/target.rs | 27 +++++++++++++++++++++++++++ src/intermediate/heading.rs | 6 +++--- src/intermediate/mod.rs | 2 ++ src/intermediate/object.rs | 12 ++++++++---- src/intermediate/page.rs | 6 +++--- src/intermediate/registry.rs | 10 +++++----- src/intermediate/target.rs | 23 +++++++++++++++++++++++ 9 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/context/target.rs create mode 100644 src/intermediate/target.rs diff --git a/src/context/mod.rs b/src/context/mod.rs index bbfa0fc..3aadf32 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -6,6 +6,7 @@ mod heading; mod object; mod plain_text; mod section; +mod target; pub(crate) use blog_post_page::RenderBlogPostPage; pub(crate) use document_element::RenderDocumentElement; diff --git a/src/context/object.rs b/src/context/object.rs index 8cccc88..c54a619 100644 --- a/src/context/object.rs +++ b/src/context/object.rs @@ -7,11 +7,13 @@ use crate::error::CustomError; use crate::intermediate::IObject; use super::plain_text::RenderPlainText; +use super::target::RenderTarget; #[derive(Debug, Serialize)] #[serde(untagged)] pub(crate) enum RenderObject { PlainText(RenderPlainText), + Target(RenderTarget), } impl RenderObject { @@ -28,6 +30,12 @@ impl RenderObject { output_file, inner, )?)), + IObject::Target(inner) => Ok(RenderObject::Target(RenderTarget::new( + config, + output_directory, + output_file, + inner, + )?)), } } } diff --git a/src/context/target.rs b/src/context/target.rs new file mode 100644 index 0000000..ebdb69c --- /dev/null +++ b/src/context/target.rs @@ -0,0 +1,27 @@ +use std::path::Path; + +use serde::Serialize; + +use crate::config::Config; +use crate::error::CustomError; +use crate::intermediate::ITarget; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "target")] +pub(crate) struct RenderTarget { + id: String, +} + +impl RenderTarget { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + target: &ITarget, + ) -> Result<RenderTarget, CustomError> { + Ok(RenderTarget { + id: target.id.clone(), + }) + } +} diff --git a/src/intermediate/heading.rs b/src/intermediate/heading.rs index 331b0b8..a12f550 100644 --- a/src/intermediate/heading.rs +++ b/src/intermediate/heading.rs @@ -10,9 +10,9 @@ pub(crate) struct IHeading { } impl IHeading { - pub(crate) fn new( - registry: &mut Registry<'_>, - heading: &organic::types::Heading<'_>, + pub(crate) fn new<'parse>( + registry: &mut Registry<'parse>, + heading: &organic::types::Heading<'parse>, ) -> Result<IHeading, CustomError> { let title = heading .title diff --git a/src/intermediate/mod.rs b/src/intermediate/mod.rs index 75aab28..95185d7 100644 --- a/src/intermediate/mod.rs +++ b/src/intermediate/mod.rs @@ -8,6 +8,7 @@ mod page; mod plain_text; mod registry; mod section; +mod target; mod util; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; @@ -18,3 +19,4 @@ pub(crate) use object::IObject; pub(crate) use page::BlogPostPage; pub(crate) use plain_text::IPlainText; pub(crate) use section::ISection; +pub(crate) use target::ITarget; diff --git a/src/intermediate/object.rs b/src/intermediate/object.rs index 87864f4..c40059b 100644 --- a/src/intermediate/object.rs +++ b/src/intermediate/object.rs @@ -2,16 +2,18 @@ use crate::error::CustomError; use super::plain_text::IPlainText; use super::registry::Registry; +use super::ITarget; #[derive(Debug)] pub(crate) enum IObject { PlainText(IPlainText), + Target(ITarget), } impl IObject { - pub(crate) fn new( - registry: &mut Registry<'_>, - obj: &organic::types::Object<'_>, + pub(crate) fn new<'parse>( + registry: &mut Registry<'parse>, + obj: &organic::types::Object<'parse>, ) -> Result<IObject, CustomError> { match obj { organic::types::Object::Bold(_) => todo!(), @@ -38,7 +40,9 @@ impl IObject { organic::types::Object::InlineBabelCall(_) => todo!(), organic::types::Object::InlineSourceBlock(_) => todo!(), organic::types::Object::LineBreak(_) => todo!(), - organic::types::Object::Target(_) => todo!(), + organic::types::Object::Target(target) => { + Ok(IObject::Target(ITarget::new(registry, target)?)) + } organic::types::Object::StatisticsCookie(_) => todo!(), organic::types::Object::Subscript(_) => todo!(), organic::types::Object::Superscript(_) => todo!(), diff --git a/src/intermediate/page.rs b/src/intermediate/page.rs index 17d7ac0..a4e9532 100644 --- a/src/intermediate/page.rs +++ b/src/intermediate/page.rs @@ -18,10 +18,10 @@ pub(crate) struct BlogPostPage { } impl BlogPostPage { - pub(crate) fn new<P: Into<PathBuf>>( + pub(crate) fn new<'parse, P: Into<PathBuf>>( path: P, - registry: &mut Registry<'_>, - document: &organic::types::Document<'_>, + registry: &mut Registry<'parse>, + document: &organic::types::Document<'parse>, ) -> Result<BlogPostPage, CustomError> { let path = path.into(); let mut children = Vec::new(); diff --git a/src/intermediate/registry.rs b/src/intermediate/registry.rs index d74e6d9..d53e2ba 100644 --- a/src/intermediate/registry.rs +++ b/src/intermediate/registry.rs @@ -2,20 +2,20 @@ use std::collections::HashMap; type IdCounter = u16; -pub(crate) struct Registry<'p> { +pub(crate) struct Registry<'parse> { id_counter: IdCounter, - targets: HashMap<&'p str, String>, + targets: HashMap<&'parse str, String>, } -impl<'p> Registry<'p> { - pub(crate) fn new() -> Registry<'p> { +impl<'parse> Registry<'parse> { + pub(crate) fn new() -> Registry<'parse> { Registry { id_counter: 0, targets: HashMap::new(), } } - pub(crate) fn get_target<'b>(&'b mut self, body: &'p str) -> &'b String { + pub(crate) fn get_target<'b>(&'b mut self, body: &'parse str) -> &'b String { self.targets.entry(body).or_insert_with(|| { self.id_counter += 1; format!("target_{}", self.id_counter) diff --git a/src/intermediate/target.rs b/src/intermediate/target.rs new file mode 100644 index 0000000..a62124b --- /dev/null +++ b/src/intermediate/target.rs @@ -0,0 +1,23 @@ +use crate::error::CustomError; +use crate::intermediate::util::coalesce_whitespace; + +use super::registry::Registry; + +#[derive(Debug)] +pub(crate) struct ITarget { + pub(crate) id: String, + value: String, +} + +impl ITarget { + pub(crate) fn new<'parse>( + registry: &mut Registry<'parse>, + target: &organic::types::Target<'parse>, + ) -> Result<ITarget, CustomError> { + let id = registry.get_target(target.value); + Ok(ITarget { + id: id.clone(), + value: target.value.to_owned(), + }) + } +} From 5b34942b64830450cacc7f019d83d8c83a36b3f6 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 15:32:24 -0400 Subject: [PATCH 43/47] Add element. --- src/context/element.rs | 17 +++++++++++++++++ src/context/section.rs | 14 ++++++++++++-- src/intermediate/element.rs | 38 +++++++++++++++++++++++++++++++++++++ src/intermediate/section.rs | 19 ++++++++++++++----- 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/context/element.rs b/src/context/element.rs index 37c605a..3825439 100644 --- a/src/context/element.rs +++ b/src/context/element.rs @@ -1,5 +1,22 @@ +use std::path::Path; + use serde::Serialize; +use crate::config::Config; +use crate::error::CustomError; +use crate::intermediate::IElement; + #[derive(Debug, Serialize)] #[serde(untagged)] pub(crate) enum RenderElement {} + +impl RenderElement { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + element: &IElement, + ) -> Result<RenderElement, CustomError> { + todo!() + } +} diff --git a/src/context/section.rs b/src/context/section.rs index 937422a..d614638 100644 --- a/src/context/section.rs +++ b/src/context/section.rs @@ -6,10 +6,14 @@ use crate::config::Config; use crate::error::CustomError; use crate::intermediate::ISection; +use super::RenderElement; + #[derive(Debug, Serialize)] #[serde(tag = "type")] #[serde(rename = "section")] -pub(crate) struct RenderSection {} +pub(crate) struct RenderSection { + children: Vec<RenderElement>, +} impl RenderSection { pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( @@ -18,6 +22,12 @@ impl RenderSection { output_file: F, section: &ISection, ) -> Result<RenderSection, CustomError> { - Ok(RenderSection {}) + let children = section + .children + .iter() + .map(|obj| RenderElement::new(config, &output_directory, &output_file, obj)) + .collect::<Result<Vec<_>, _>>()?; + + Ok(RenderSection { children }) } } diff --git a/src/intermediate/element.rs b/src/intermediate/element.rs index 3aeafe0..36bc9b6 100644 --- a/src/intermediate/element.rs +++ b/src/intermediate/element.rs @@ -1,2 +1,40 @@ +use crate::error::CustomError; + +use super::registry::Registry; + #[derive(Debug)] pub(crate) enum IElement {} + +impl IElement { + pub(crate) fn new<'parse>( + registry: &mut Registry<'parse>, + elem: &organic::types::Element<'parse>, + ) -> Result<IElement, CustomError> { + match elem { + organic::types::Element::Paragraph(_) => todo!(), + organic::types::Element::PlainList(_) => todo!(), + organic::types::Element::CenterBlock(_) => todo!(), + organic::types::Element::QuoteBlock(_) => todo!(), + organic::types::Element::SpecialBlock(_) => todo!(), + organic::types::Element::DynamicBlock(_) => todo!(), + organic::types::Element::FootnoteDefinition(_) => todo!(), + organic::types::Element::Comment(_) => todo!(), + organic::types::Element::Drawer(_) => todo!(), + organic::types::Element::PropertyDrawer(_) => todo!(), + organic::types::Element::Table(_) => todo!(), + organic::types::Element::VerseBlock(_) => todo!(), + organic::types::Element::CommentBlock(_) => todo!(), + organic::types::Element::ExampleBlock(_) => todo!(), + organic::types::Element::ExportBlock(_) => todo!(), + organic::types::Element::SrcBlock(_) => todo!(), + organic::types::Element::Clock(_) => todo!(), + organic::types::Element::DiarySexp(_) => todo!(), + organic::types::Element::Planning(_) => todo!(), + organic::types::Element::FixedWidthArea(_) => todo!(), + organic::types::Element::HorizontalRule(_) => todo!(), + organic::types::Element::Keyword(_) => todo!(), + organic::types::Element::BabelCall(_) => todo!(), + organic::types::Element::LatexEnvironment(_) => todo!(), + } + } +} diff --git a/src/intermediate/section.rs b/src/intermediate/section.rs index 2afd831..5e129e9 100644 --- a/src/intermediate/section.rs +++ b/src/intermediate/section.rs @@ -1,15 +1,24 @@ use crate::error::CustomError; use super::registry::Registry; +use super::IElement; #[derive(Debug)] -pub(crate) struct ISection {} +pub(crate) struct ISection { + pub(crate) children: Vec<IElement>, +} impl ISection { - pub(crate) fn new( - registry: &mut Registry<'_>, - section: &organic::types::Section<'_>, + pub(crate) fn new<'parse>( + registry: &mut Registry<'parse>, + section: &organic::types::Section<'parse>, ) -> Result<ISection, CustomError> { - Ok(ISection {}) + let children = section + .children + .iter() + .map(|obj| IElement::new(registry, obj)) + .collect::<Result<Vec<_>, _>>()?; + + Ok(ISection { children }) } } From 4a6948cde767578b4a91dbfab20f874ccabafb9a Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 15:46:16 -0400 Subject: [PATCH 44/47] Add paragraph. --- src/context/element.rs | 15 +++++++++++++-- src/context/mod.rs | 1 + src/context/paragraph.rs | 32 ++++++++++++++++++++++++++++++++ src/intermediate/element.rs | 9 +++++++-- src/intermediate/mod.rs | 2 ++ src/intermediate/object.rs | 8 ++++---- src/intermediate/paragraph.rs | 24 ++++++++++++++++++++++++ 7 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 src/context/paragraph.rs create mode 100644 src/intermediate/paragraph.rs diff --git a/src/context/element.rs b/src/context/element.rs index 3825439..7a09a62 100644 --- a/src/context/element.rs +++ b/src/context/element.rs @@ -6,9 +6,13 @@ use crate::config::Config; use crate::error::CustomError; use crate::intermediate::IElement; +use super::paragraph::RenderParagraph; + #[derive(Debug, Serialize)] #[serde(untagged)] -pub(crate) enum RenderElement {} +pub(crate) enum RenderElement { + Paragraph(RenderParagraph), +} impl RenderElement { pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( @@ -17,6 +21,13 @@ impl RenderElement { output_file: F, element: &IElement, ) -> Result<RenderElement, CustomError> { - todo!() + match element { + IElement::Paragraph(inner) => Ok(RenderElement::Paragraph(RenderParagraph::new( + config, + output_directory, + output_file, + inner, + )?)), + } } } diff --git a/src/context/mod.rs b/src/context/mod.rs index 3aadf32..d95cbee 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -4,6 +4,7 @@ mod element; mod global_settings; mod heading; mod object; +mod paragraph; mod plain_text; mod section; mod target; diff --git a/src/context/paragraph.rs b/src/context/paragraph.rs new file mode 100644 index 0000000..b2760f0 --- /dev/null +++ b/src/context/paragraph.rs @@ -0,0 +1,32 @@ +use std::path::Path; + +use serde::Serialize; + +use crate::config::Config; +use crate::error::CustomError; +use crate::intermediate::IParagraph; + +use super::RenderObject; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "heading")] +pub(crate) struct RenderParagraph { + children: Vec<RenderObject>, +} + +impl RenderParagraph { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + paragraph: &IParagraph, + ) -> Result<RenderParagraph, CustomError> { + let children = paragraph + .children + .iter() + .map(|obj| RenderObject::new(config, &output_directory, &output_file, obj)) + .collect::<Result<Vec<_>, _>>()?; + Ok(RenderParagraph { children }) + } +} diff --git a/src/intermediate/element.rs b/src/intermediate/element.rs index 36bc9b6..1b1370c 100644 --- a/src/intermediate/element.rs +++ b/src/intermediate/element.rs @@ -1,9 +1,12 @@ use crate::error::CustomError; use super::registry::Registry; +use super::IParagraph; #[derive(Debug)] -pub(crate) enum IElement {} +pub(crate) enum IElement { + Paragraph(IParagraph), +} impl IElement { pub(crate) fn new<'parse>( @@ -11,7 +14,9 @@ impl IElement { elem: &organic::types::Element<'parse>, ) -> Result<IElement, CustomError> { match elem { - organic::types::Element::Paragraph(_) => todo!(), + organic::types::Element::Paragraph(inner) => { + Ok(IElement::Paragraph(IParagraph::new(registry, inner)?)) + } organic::types::Element::PlainList(_) => todo!(), organic::types::Element::CenterBlock(_) => todo!(), organic::types::Element::QuoteBlock(_) => todo!(), diff --git a/src/intermediate/mod.rs b/src/intermediate/mod.rs index 95185d7..eedc589 100644 --- a/src/intermediate/mod.rs +++ b/src/intermediate/mod.rs @@ -5,6 +5,7 @@ mod element; mod heading; mod object; mod page; +mod paragraph; mod plain_text; mod registry; mod section; @@ -17,6 +18,7 @@ pub(crate) use element::IElement; pub(crate) use heading::IHeading; pub(crate) use object::IObject; pub(crate) use page::BlogPostPage; +pub(crate) use paragraph::IParagraph; pub(crate) use plain_text::IPlainText; pub(crate) use section::ISection; pub(crate) use target::ITarget; diff --git a/src/intermediate/object.rs b/src/intermediate/object.rs index c40059b..5b74111 100644 --- a/src/intermediate/object.rs +++ b/src/intermediate/object.rs @@ -22,8 +22,8 @@ impl IObject { 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(registry, plain_text)?)) + organic::types::Object::PlainText(inner) => { + Ok(IObject::PlainText(IPlainText::new(registry, inner)?)) } organic::types::Object::RegularLink(_) => todo!(), organic::types::Object::RadioLink(_) => todo!(), @@ -40,8 +40,8 @@ impl IObject { organic::types::Object::InlineBabelCall(_) => todo!(), organic::types::Object::InlineSourceBlock(_) => todo!(), organic::types::Object::LineBreak(_) => todo!(), - organic::types::Object::Target(target) => { - Ok(IObject::Target(ITarget::new(registry, target)?)) + organic::types::Object::Target(inner) => { + Ok(IObject::Target(ITarget::new(registry, inner)?)) } organic::types::Object::StatisticsCookie(_) => todo!(), organic::types::Object::Subscript(_) => todo!(), diff --git a/src/intermediate/paragraph.rs b/src/intermediate/paragraph.rs new file mode 100644 index 0000000..e40a47f --- /dev/null +++ b/src/intermediate/paragraph.rs @@ -0,0 +1,24 @@ +use crate::error::CustomError; + +use super::registry::Registry; +use super::IObject; + +#[derive(Debug)] +pub(crate) struct IParagraph { + pub(crate) children: Vec<IObject>, +} + +impl IParagraph { + pub(crate) fn new<'parse>( + registry: &mut Registry<'parse>, + paragraph: &organic::types::Paragraph<'parse>, + ) -> Result<IParagraph, CustomError> { + let children = paragraph + .children + .iter() + .map(|obj| IObject::new(registry, obj)) + .collect::<Result<Vec<_>, _>>()?; + + Ok(IParagraph { children }) + } +} From f9377d76090ca5ec91ac79a07539332522863f99 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 15:55:19 -0400 Subject: [PATCH 45/47] Make converstion to intermediate state async. We are going to need to do things like call external tools for syntax highlighting so we are going to need async in there eventually. --- src/intermediate/definition.rs | 13 ++++++++----- src/intermediate/element.rs | 4 ++-- src/intermediate/heading.rs | 14 ++++++++------ src/intermediate/object.rs | 6 +++--- src/intermediate/page.rs | 10 +++++++--- src/intermediate/paragraph.rs | 14 ++++++++------ src/intermediate/plain_text.rs | 6 +++--- src/intermediate/section.rs | 14 ++++++++------ src/intermediate/target.rs | 2 +- 9 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/intermediate/definition.rs b/src/intermediate/definition.rs index 897b49a..2f62ca7 100644 --- a/src/intermediate/definition.rs +++ b/src/intermediate/definition.rs @@ -61,11 +61,14 @@ impl BlogPost { let mut ret = Vec::new(); for (real_path, _contents, parsed_document) in parsed_org_files.iter() { let relative_to_post_dir_path = real_path.strip_prefix(post_dir)?; - ret.push(BlogPostPage::new( - relative_to_post_dir_path, - &mut registry, - parsed_document, - )?); + ret.push( + BlogPostPage::new( + relative_to_post_dir_path, + &mut registry, + parsed_document, + ) + .await?, + ); } ret }; diff --git a/src/intermediate/element.rs b/src/intermediate/element.rs index 1b1370c..c891a1a 100644 --- a/src/intermediate/element.rs +++ b/src/intermediate/element.rs @@ -9,13 +9,13 @@ pub(crate) enum IElement { } impl IElement { - pub(crate) fn new<'parse>( + pub(crate) async fn new<'parse>( registry: &mut Registry<'parse>, elem: &organic::types::Element<'parse>, ) -> Result<IElement, CustomError> { match elem { organic::types::Element::Paragraph(inner) => { - Ok(IElement::Paragraph(IParagraph::new(registry, inner)?)) + Ok(IElement::Paragraph(IParagraph::new(registry, inner).await?)) } organic::types::Element::PlainList(_) => todo!(), organic::types::Element::CenterBlock(_) => todo!(), diff --git a/src/intermediate/heading.rs b/src/intermediate/heading.rs index a12f550..17cc680 100644 --- a/src/intermediate/heading.rs +++ b/src/intermediate/heading.rs @@ -10,15 +10,17 @@ pub(crate) struct IHeading { } impl IHeading { - pub(crate) fn new<'parse>( + pub(crate) async fn new<'parse>( registry: &mut Registry<'parse>, heading: &organic::types::Heading<'parse>, ) -> Result<IHeading, CustomError> { - let title = heading - .title - .iter() - .map(|obj| IObject::new(registry, obj)) - .collect::<Result<Vec<_>, _>>()?; + let title = { + let mut ret = Vec::new(); + for obj in heading.title.iter() { + ret.push(IObject::new(registry, obj).await?); + } + ret + }; Ok(IHeading { title, level: heading.level, diff --git a/src/intermediate/object.rs b/src/intermediate/object.rs index 5b74111..3506a00 100644 --- a/src/intermediate/object.rs +++ b/src/intermediate/object.rs @@ -11,7 +11,7 @@ pub(crate) enum IObject { } impl IObject { - pub(crate) fn new<'parse>( + pub(crate) async fn new<'parse>( registry: &mut Registry<'parse>, obj: &organic::types::Object<'parse>, ) -> Result<IObject, CustomError> { @@ -23,7 +23,7 @@ impl IObject { organic::types::Object::Code(_) => todo!(), organic::types::Object::Verbatim(_) => todo!(), organic::types::Object::PlainText(inner) => { - Ok(IObject::PlainText(IPlainText::new(registry, inner)?)) + Ok(IObject::PlainText(IPlainText::new(registry, inner).await?)) } organic::types::Object::RegularLink(_) => todo!(), organic::types::Object::RadioLink(_) => todo!(), @@ -41,7 +41,7 @@ impl IObject { organic::types::Object::InlineSourceBlock(_) => todo!(), organic::types::Object::LineBreak(_) => todo!(), organic::types::Object::Target(inner) => { - Ok(IObject::Target(ITarget::new(registry, inner)?)) + Ok(IObject::Target(ITarget::new(registry, inner).await?)) } organic::types::Object::StatisticsCookie(_) => todo!(), organic::types::Object::Subscript(_) => todo!(), diff --git a/src/intermediate/page.rs b/src/intermediate/page.rs index a4e9532..c512d46 100644 --- a/src/intermediate/page.rs +++ b/src/intermediate/page.rs @@ -18,7 +18,7 @@ pub(crate) struct BlogPostPage { } impl BlogPostPage { - pub(crate) fn new<'parse, P: Into<PathBuf>>( + pub(crate) async fn new<'parse, P: Into<PathBuf>>( path: P, registry: &mut Registry<'parse>, document: &organic::types::Document<'parse>, @@ -26,10 +26,14 @@ impl BlogPostPage { let path = path.into(); let mut children = Vec::new(); if let Some(section) = document.zeroth_section.as_ref() { - children.push(IDocumentElement::Section(ISection::new(registry, section)?)); + children.push(IDocumentElement::Section( + ISection::new(registry, section).await?, + )); } for heading in document.children.iter() { - children.push(IDocumentElement::Heading(IHeading::new(registry, heading)?)); + children.push(IDocumentElement::Heading( + IHeading::new(registry, heading).await?, + )); } Ok(BlogPostPage { diff --git a/src/intermediate/paragraph.rs b/src/intermediate/paragraph.rs index e40a47f..09cf1be 100644 --- a/src/intermediate/paragraph.rs +++ b/src/intermediate/paragraph.rs @@ -9,15 +9,17 @@ pub(crate) struct IParagraph { } impl IParagraph { - pub(crate) fn new<'parse>( + pub(crate) async fn new<'parse>( registry: &mut Registry<'parse>, paragraph: &organic::types::Paragraph<'parse>, ) -> Result<IParagraph, CustomError> { - let children = paragraph - .children - .iter() - .map(|obj| IObject::new(registry, obj)) - .collect::<Result<Vec<_>, _>>()?; + let children = { + let mut ret = Vec::new(); + for obj in paragraph.children.iter() { + ret.push(IObject::new(registry, obj).await?); + } + ret + }; Ok(IParagraph { children }) } diff --git a/src/intermediate/plain_text.rs b/src/intermediate/plain_text.rs index f794680..490c843 100644 --- a/src/intermediate/plain_text.rs +++ b/src/intermediate/plain_text.rs @@ -9,9 +9,9 @@ pub(crate) struct IPlainText { } impl IPlainText { - pub(crate) fn new( - registry: &mut Registry<'_>, - plain_text: &organic::types::PlainText<'_>, + pub(crate) async fn new<'parse>( + registry: &mut Registry<'parse>, + plain_text: &organic::types::PlainText<'parse>, ) -> Result<IPlainText, CustomError> { Ok(IPlainText { source: coalesce_whitespace(plain_text.source).into_owned(), diff --git a/src/intermediate/section.rs b/src/intermediate/section.rs index 5e129e9..c9426e6 100644 --- a/src/intermediate/section.rs +++ b/src/intermediate/section.rs @@ -9,15 +9,17 @@ pub(crate) struct ISection { } impl ISection { - pub(crate) fn new<'parse>( + pub(crate) async fn new<'parse>( registry: &mut Registry<'parse>, section: &organic::types::Section<'parse>, ) -> Result<ISection, CustomError> { - let children = section - .children - .iter() - .map(|obj| IElement::new(registry, obj)) - .collect::<Result<Vec<_>, _>>()?; + let children = { + let mut ret = Vec::new(); + for elem in section.children.iter() { + ret.push(IElement::new(registry, elem).await?); + } + ret + }; Ok(ISection { children }) } diff --git a/src/intermediate/target.rs b/src/intermediate/target.rs index a62124b..1a594de 100644 --- a/src/intermediate/target.rs +++ b/src/intermediate/target.rs @@ -10,7 +10,7 @@ pub(crate) struct ITarget { } impl ITarget { - pub(crate) fn new<'parse>( + pub(crate) async fn new<'parse>( registry: &mut Registry<'parse>, target: &organic::types::Target<'parse>, ) -> Result<ITarget, CustomError> { From 5891ac7fb73bb9ad88baccd077479091b27b5738 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 16:09:44 -0400 Subject: [PATCH 46/47] Add keyword and as no-op. --- src/context/element.rs | 8 ++++++++ src/context/keyword.rs | 23 +++++++++++++++++++++++ src/context/mod.rs | 1 + src/context/paragraph.rs | 2 +- src/context/plain_text.rs | 2 +- src/intermediate/element.rs | 6 +++++- src/intermediate/keyword.rs | 17 +++++++++++++++++ src/intermediate/mod.rs | 2 ++ 8 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/context/keyword.rs create mode 100644 src/intermediate/keyword.rs diff --git a/src/context/element.rs b/src/context/element.rs index 7a09a62..8a3cf0b 100644 --- a/src/context/element.rs +++ b/src/context/element.rs @@ -6,12 +6,14 @@ use crate::config::Config; use crate::error::CustomError; use crate::intermediate::IElement; +use super::keyword::RenderKeyword; use super::paragraph::RenderParagraph; #[derive(Debug, Serialize)] #[serde(untagged)] pub(crate) enum RenderElement { Paragraph(RenderParagraph), + Keyword(RenderKeyword), } impl RenderElement { @@ -28,6 +30,12 @@ impl RenderElement { output_file, inner, )?)), + IElement::Keyword(inner) => Ok(RenderElement::Keyword(RenderKeyword::new( + config, + output_directory, + output_file, + inner, + )?)), } } } diff --git a/src/context/keyword.rs b/src/context/keyword.rs new file mode 100644 index 0000000..c9e7a10 --- /dev/null +++ b/src/context/keyword.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use serde::Serialize; + +use crate::config::Config; +use crate::error::CustomError; +use crate::intermediate::IKeyword; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "keyword")] +pub(crate) struct RenderKeyword {} + +impl RenderKeyword { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + keyword: &IKeyword, + ) -> Result<RenderKeyword, CustomError> { + Ok(RenderKeyword {}) + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index d95cbee..f9b5e75 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -3,6 +3,7 @@ mod document_element; mod element; mod global_settings; mod heading; +mod keyword; mod object; mod paragraph; mod plain_text; diff --git a/src/context/paragraph.rs b/src/context/paragraph.rs index b2760f0..2570f93 100644 --- a/src/context/paragraph.rs +++ b/src/context/paragraph.rs @@ -10,7 +10,7 @@ use super::RenderObject; #[derive(Debug, Serialize)] #[serde(tag = "type")] -#[serde(rename = "heading")] +#[serde(rename = "paragraph")] pub(crate) struct RenderParagraph { children: Vec<RenderObject>, } diff --git a/src/context/plain_text.rs b/src/context/plain_text.rs index 24bf142..76f3d78 100644 --- a/src/context/plain_text.rs +++ b/src/context/plain_text.rs @@ -8,7 +8,7 @@ use crate::intermediate::IPlainText; #[derive(Debug, Serialize)] #[serde(tag = "type")] -#[serde(rename = "heading")] +#[serde(rename = "plain_text")] pub(crate) struct RenderPlainText {} impl RenderPlainText { diff --git a/src/intermediate/element.rs b/src/intermediate/element.rs index c891a1a..e9b3feb 100644 --- a/src/intermediate/element.rs +++ b/src/intermediate/element.rs @@ -1,11 +1,13 @@ use crate::error::CustomError; +use super::keyword::IKeyword; use super::registry::Registry; use super::IParagraph; #[derive(Debug)] pub(crate) enum IElement { Paragraph(IParagraph), + Keyword(IKeyword), } impl IElement { @@ -37,7 +39,9 @@ impl IElement { organic::types::Element::Planning(_) => todo!(), organic::types::Element::FixedWidthArea(_) => todo!(), organic::types::Element::HorizontalRule(_) => todo!(), - organic::types::Element::Keyword(_) => todo!(), + organic::types::Element::Keyword(inner) => { + Ok(IElement::Keyword(IKeyword::new(registry, inner).await?)) + } organic::types::Element::BabelCall(_) => todo!(), organic::types::Element::LatexEnvironment(_) => todo!(), } diff --git a/src/intermediate/keyword.rs b/src/intermediate/keyword.rs new file mode 100644 index 0000000..876eff1 --- /dev/null +++ b/src/intermediate/keyword.rs @@ -0,0 +1,17 @@ +use crate::error::CustomError; + +use super::registry::Registry; +use super::IObject; + +/// Essentially a no-op since the keyword is not rendered and any relevant impact on other elements is pulled from the parsed form of keyword. +#[derive(Debug)] +pub(crate) struct IKeyword {} + +impl IKeyword { + pub(crate) async fn new<'parse>( + registry: &mut Registry<'parse>, + keyword: &organic::types::Keyword<'parse>, + ) -> Result<IKeyword, CustomError> { + Ok(IKeyword {}) + } +} diff --git a/src/intermediate/mod.rs b/src/intermediate/mod.rs index eedc589..293b609 100644 --- a/src/intermediate/mod.rs +++ b/src/intermediate/mod.rs @@ -3,6 +3,7 @@ mod definition; mod document_element; mod element; mod heading; +mod keyword; mod object; mod page; mod paragraph; @@ -16,6 +17,7 @@ pub(crate) use definition::BlogPost; pub(crate) use document_element::IDocumentElement; pub(crate) use element::IElement; pub(crate) use heading::IHeading; +pub(crate) use keyword::IKeyword; pub(crate) use object::IObject; pub(crate) use page::BlogPostPage; pub(crate) use paragraph::IParagraph; From 354d24cf696d4bc4904c1649d6a2166b9d044414 Mon Sep 17 00:00:00 2001 From: Tom Alexander <tom@fizz.buzz> Date: Fri, 27 Oct 2023 16:13:23 -0400 Subject: [PATCH 47/47] Add comment as a no-op. --- src/context/comment.rs | 23 +++++++++++++++++++++++ src/context/element.rs | 8 ++++++++ src/context/mod.rs | 1 + src/intermediate/comment.rs | 16 ++++++++++++++++ src/intermediate/element.rs | 6 +++++- src/intermediate/keyword.rs | 1 - src/intermediate/mod.rs | 2 ++ 7 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/context/comment.rs create mode 100644 src/intermediate/comment.rs diff --git a/src/context/comment.rs b/src/context/comment.rs new file mode 100644 index 0000000..713c220 --- /dev/null +++ b/src/context/comment.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use serde::Serialize; + +use crate::config::Config; +use crate::error::CustomError; +use crate::intermediate::IComment; + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +#[serde(rename = "comment")] +pub(crate) struct RenderComment {} + +impl RenderComment { + pub(crate) fn new<D: AsRef<Path>, F: AsRef<Path>>( + config: &Config, + output_directory: D, + output_file: F, + comment: &IComment, + ) -> Result<RenderComment, CustomError> { + Ok(RenderComment {}) + } +} diff --git a/src/context/element.rs b/src/context/element.rs index 8a3cf0b..5144938 100644 --- a/src/context/element.rs +++ b/src/context/element.rs @@ -6,6 +6,7 @@ use crate::config::Config; use crate::error::CustomError; use crate::intermediate::IElement; +use super::comment::RenderComment; use super::keyword::RenderKeyword; use super::paragraph::RenderParagraph; @@ -14,6 +15,7 @@ use super::paragraph::RenderParagraph; pub(crate) enum RenderElement { Paragraph(RenderParagraph), Keyword(RenderKeyword), + Comment(RenderComment), } impl RenderElement { @@ -36,6 +38,12 @@ impl RenderElement { output_file, inner, )?)), + IElement::Comment(inner) => Ok(RenderElement::Comment(RenderComment::new( + config, + output_directory, + output_file, + inner, + )?)), } } } diff --git a/src/context/mod.rs b/src/context/mod.rs index f9b5e75..96a8811 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -1,4 +1,5 @@ mod blog_post_page; +mod comment; mod document_element; mod element; mod global_settings; diff --git a/src/intermediate/comment.rs b/src/intermediate/comment.rs new file mode 100644 index 0000000..70649f2 --- /dev/null +++ b/src/intermediate/comment.rs @@ -0,0 +1,16 @@ +use crate::error::CustomError; + +use super::registry::Registry; + +/// Essentially a no-op since the comment is not rendered. +#[derive(Debug)] +pub(crate) struct IComment {} + +impl IComment { + pub(crate) async fn new<'parse>( + registry: &mut Registry<'parse>, + comment: &organic::types::Comment<'parse>, + ) -> Result<IComment, CustomError> { + Ok(IComment {}) + } +} diff --git a/src/intermediate/element.rs b/src/intermediate/element.rs index e9b3feb..fd2b4f1 100644 --- a/src/intermediate/element.rs +++ b/src/intermediate/element.rs @@ -1,5 +1,6 @@ use crate::error::CustomError; +use super::comment::IComment; use super::keyword::IKeyword; use super::registry::Registry; use super::IParagraph; @@ -8,6 +9,7 @@ use super::IParagraph; pub(crate) enum IElement { Paragraph(IParagraph), Keyword(IKeyword), + Comment(IComment), } impl IElement { @@ -25,7 +27,9 @@ impl IElement { organic::types::Element::SpecialBlock(_) => todo!(), organic::types::Element::DynamicBlock(_) => todo!(), organic::types::Element::FootnoteDefinition(_) => todo!(), - organic::types::Element::Comment(_) => todo!(), + organic::types::Element::Comment(inner) => { + Ok(IElement::Comment(IComment::new(registry, inner).await?)) + } organic::types::Element::Drawer(_) => todo!(), organic::types::Element::PropertyDrawer(_) => todo!(), organic::types::Element::Table(_) => todo!(), diff --git a/src/intermediate/keyword.rs b/src/intermediate/keyword.rs index 876eff1..bd26939 100644 --- a/src/intermediate/keyword.rs +++ b/src/intermediate/keyword.rs @@ -1,7 +1,6 @@ use crate::error::CustomError; use super::registry::Registry; -use super::IObject; /// Essentially a no-op since the keyword is not rendered and any relevant impact on other elements is pulled from the parsed form of keyword. #[derive(Debug)] diff --git a/src/intermediate/mod.rs b/src/intermediate/mod.rs index 293b609..b363853 100644 --- a/src/intermediate/mod.rs +++ b/src/intermediate/mod.rs @@ -1,3 +1,4 @@ +mod comment; mod convert; mod definition; mod document_element; @@ -12,6 +13,7 @@ mod registry; mod section; mod target; mod util; +pub(crate) use comment::IComment; pub(crate) use convert::convert_blog_post_page_to_render_context; pub(crate) use definition::BlogPost; pub(crate) use document_element::IDocumentElement;