use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use anyhow::{bail, Context, Result};
use async_std::task::{spawn, JoinHandle};
use nipper::Document;
use super::{AssetFile, LinkAttrs, TrunkLinkPipelineOutput, ATTR_HREF, ATTR_TYPE};
pub struct Inline {
id: usize,
asset: AssetFile,
content_type: ContentType,
}
impl Inline {
pub const TYPE_INLINE: &'static str = "inline";
pub async fn new(html_dir: Arc<PathBuf>, attrs: LinkAttrs, id: usize) -> Result<Self> {
let href_attr = attrs
.get(ATTR_HREF)
.context(r#"required attr `href` missing for <link data-trunk rel="inline" .../> element"#)?;
let mut path = PathBuf::new();
path.extend(href_attr.split('/'));
let asset = AssetFile::new(&html_dir, path).await?;
let content_type = ContentType::from_attr_or_ext(attrs.get(ATTR_TYPE), asset.ext.as_deref())?;
Ok(Self { id, asset, content_type })
}
#[tracing::instrument(level = "trace", skip(self))]
pub fn spawn(self) -> JoinHandle<Result<TrunkLinkPipelineOutput>> {
spawn(self.run())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn run(self) -> Result<TrunkLinkPipelineOutput> {
let rel_path = crate::common::strip_prefix(&self.asset.path);
tracing::info!(path = ?rel_path, "reading file content");
let content = self.asset.read_to_string().await?;
tracing::info!(path = ?rel_path, "finished reading file content");
Ok(TrunkLinkPipelineOutput::Inline(InlineOutput {
id: self.id,
content,
content_type: self.content_type,
}))
}
}
pub enum ContentType {
Html,
Css,
Js,
}
impl ContentType {
fn from_attr_or_ext(attr: Option<impl AsRef<str>>, ext: Option<&str>) -> Result<Self> {
match attr {
Some(attr) => Self::from_str(attr.as_ref()),
None => match ext {
Some(ext) => Self::from_str(ext),
None => bail!(
r#"unknown type value for <link data-trunk rel="inline" .../> attr; please ensure the value is lowercase and is a supported content type"#,
),
},
}
}
}
impl FromStr for ContentType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"html" => Ok(Self::Html),
"css" => Ok(Self::Css),
"js" => Ok(Self::Js),
s => bail!(
r#"unknown `type="{}"` value for <link data-trunk rel="inline" .../> attr; please ensure the value is lowercase and is a supported content type"#,
s
),
}
}
}
pub struct InlineOutput {
pub id: usize,
pub content: String,
pub content_type: ContentType,
}
impl InlineOutput {
pub async fn finalize(self, dom: &mut Document) -> Result<()> {
let html = match self.content_type {
ContentType::Html => self.content,
ContentType::Css => format!(r#"<style type="text/css">{}</style>"#, self.content),
ContentType::Js => format!(r#"<script>{}</script>"#, self.content),
};
dom.select(&super::trunk_id_selector(self.id)).replace_with_html(html);
Ok(())
}
}