use camino::{Utf8Path, Utf8PathBuf};
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::{
Blueprint, Environment, Output,
error::HauchiwaError,
graph::Handle,
loader::{GlobAssetsTask, Input, Store},
page::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 metadata: T,
pub path: Utf8PathBuf,
pub body: String,
}
impl<T> Document<T> {
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 href(&self, dir: impl AsRef<Utf8Path>) -> String {
let path = self
.path
.strip_prefix(dir.as_ref().as_std_path())
.unwrap_or(&self.path);
let mut url = String::from("/");
if let Some(parent) = path.parent() {
url.push_str(parent.as_str());
}
let stem = path.file_stem().unwrap_or_default();
if stem != "index" {
if !url.ends_with('/') {
url.push('/');
}
url.push_str(stem);
}
if !url.ends_with('/') {
url.push('/');
}
if url.starts_with("//") {
url.replace("//", "/")
} else {
url
}
}
pub fn dist_path(&self, src: impl AsRef<Utf8Path>, out: impl AsRef<Utf8Path>) -> Utf8PathBuf {
out.as_ref()
.join(self.href(src).trim_start_matches('/'))
.join("index.html")
}
pub fn output(&self) -> OutputBuilder {
Output::mapper(&self.path)
}
}
impl<G> Blueprint<G>
where
G: Send + Sync + 'static,
{
pub fn load<R>(
&mut self,
path_glob: &'static str,
callback: impl Fn(&Environment<G>, &mut Store, Input) -> anyhow::Result<R>
+ Send
+ Sync
+ 'static,
) -> Result<Handle<super::Assets<R>>, HauchiwaError>
where
G: Send + Sync + 'static,
R: Send + Sync + 'static,
{
Ok(self.add_task_opaque(GlobAssetsTask::new(
vec![path_glob],
vec![path_glob],
move |ctx, store, input| {
let path = input.path.clone();
let data = callback(ctx.env, store, input)?;
Ok((path, data))
},
)?))
}
pub fn load_documents<R>(
&mut self,
path_glob: &'static str,
) -> Result<Handle<super::Assets<Document<R>>>, HauchiwaError>
where
G: Send + Sync + 'static,
R: DeserializeOwned + Send + Sync + 'static,
{
Ok(self.add_task_opaque(GlobAssetsTask::new(
vec![path_glob],
vec![path_glob],
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)?;
Ok((
input.path.clone(),
Document {
path: input.path,
metadata,
body: content,
},
))
},
)?))
}
}