natter/src/blog_post/definition.rs
Tom Alexander e543a5db74
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.
2023-10-23 16:03:37 -04:00

93 lines
2.9 KiB
Rust

use std::path::Path;
use std::path::PathBuf;
use tokio::task::JoinHandle;
use walkdir::WalkDir;
use crate::error::CustomError;
use super::BlogPostPage;
#[derive(Debug)]
pub(crate) struct BlogPost {
pub(crate) id: String,
pages: Vec<BlogPostPage>,
}
impl BlogPost {
pub(crate) async fn load_blog_post<P: AsRef<Path>, R: AsRef<Path>>(
root_dir: R,
post_dir: P,
) -> 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.");
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))
.map_err(|_| CustomError::Static("Failed to parse org-mode document."))?;
ret.push((path, contents, parsed));
}
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
}
}
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<P: AsRef<Path>>(
root_dir: P,
) -> Result<impl Iterator<Item = JoinHandle<std::io::Result<(PathBuf, String)>>>, 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::<Result<Vec<_>, _>>()?;
let org_files = org_files
.into_iter()
.map(walkdir::DirEntry::into_path)
.map(|path| tokio::spawn(read_file(path)));
Ok(org_files)
}