use std::sync::Arc;
use camino::{Utf8Path, Utf8PathBuf};
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::{
Blueprint, Output,
engine::Many,
error::HauchiwaError,
loader::{GlobFiles, Input},
output::OutputBuilder,
};
#[derive(Debug, Error)]
pub enum FrontmatterError {
#[error("UTF-8 conversion error: {0}")]
Utf8(#[from] std::str::Utf8Error),
#[error("Frontmatter parsing error: {0}")]
Parse(anyhow::Error),
}
#[derive(Clone)]
pub struct Document<T> {
pub matter: Box<T>,
pub text: String,
pub meta: DocumentMeta,
}
#[derive(Debug, Clone)]
pub struct DocumentMeta {
pub path: Utf8PathBuf,
pub offset: Option<Arc<str>>,
pub href: String,
}
impl DocumentMeta {
pub fn slug(&self) -> &str {
let stem = self.path.file_stem().unwrap_or_default();
if stem == "index" {
self.path
.parent()
.and_then(|p| p.file_name())
.unwrap_or(stem)
} else {
stem
}
}
pub fn dist_path(&self, out: impl AsRef<Utf8Path>) -> Utf8PathBuf {
crate::output::href_to_dist(&self.href, out)
}
pub fn assets(&self, pattern: &str) -> String {
let base = crate::output::source_to_bundle(&self.path);
base.join(pattern).to_string()
}
pub fn resolve(&self, path: impl AsRef<str>) -> Utf8PathBuf {
let base = crate::output::source_to_bundle(&self.path);
let joined = base.join(path.as_ref());
crate::output::normalize_path(&joined)
}
}
impl<T> Document<T> {
pub fn output(&self) -> OutputBuilder {
Output::mapper(&self.meta.path)
}
}
pub struct DocumentLoader<'a, G, R>
where
G: Send + Sync,
R: DeserializeOwned + Send + Sync + 'static,
{
blueprint: &'a mut Blueprint<G>,
sources: Vec<String>,
offset: Option<String>,
_phantom: std::marker::PhantomData<R>,
}
impl<'a, G, R> DocumentLoader<'a, G, R>
where
G: Send + Sync + 'static,
R: DeserializeOwned + Send + Sync + 'static,
{
fn new(blueprint: &'a mut Blueprint<G>) -> Self {
Self {
blueprint,
sources: Vec::new(),
offset: None,
_phantom: std::marker::PhantomData,
}
}
pub fn source(mut self, glob: impl Into<String>) -> Self {
self.sources.push(glob.into());
self
}
pub fn offset(mut self, offset: impl Into<String>) -> Self {
self.offset = Some(offset.into());
self
}
pub fn register(self) -> Result<Many<Document<R>>, HauchiwaError> {
let offset = self.offset.map(Arc::from);
let task = GlobFiles::new(
self.sources.clone(),
self.sources,
move |_, _, input: Input| {
let bytes = input
.read()
.map_err(|e| FrontmatterError::Parse(e.into()))?;
let data = std::str::from_utf8(&bytes).map_err(FrontmatterError::Utf8)?;
let (metadata, content) =
super::parse_yaml::<R>(data).map_err(FrontmatterError::Parse)?;
let href = crate::output::source_to_href(&input.path, offset.as_deref());
Ok((
input.path.clone(),
Document {
matter: Box::new(metadata),
text: content,
meta: DocumentMeta {
path: input.path,
offset: offset.clone(),
href,
},
},
))
},
)?;
Ok(self.blueprint.add_task_fine(task))
}
}
impl<G> Blueprint<G>
where
G: Send + Sync + 'static,
{
pub fn load_documents<R>(&mut self) -> DocumentLoader<'_, G, R>
where
G: Send + Sync + 'static,
R: DeserializeOwned + Send + Sync + 'static,
{
DocumentLoader::new(self)
}
}