e3b5f7f74f
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.
96 lines
3.0 KiB
Rust
96 lines
3.0 KiB
Rust
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
use tokio::task::JoinHandle;
|
|
use walkdir::WalkDir;
|
|
|
|
use crate::error::CustomError;
|
|
|
|
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 {
|
|
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,
|
|
children: Vec::new(),
|
|
})
|
|
}
|
|
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)
|
|
}
|