use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use nipper::Document;
use tokio::fs;
use tokio::task::JoinHandle;
use super::{AssetFile, LinkAttrs, TrunkLinkPipelineOutput, ATTR_HREF, ATTR_INLINE};
use crate::common;
use crate::config::RtcBuild;
use crate::tools::{self, Application};
pub struct Sass {
id: usize,
cfg: Arc<RtcBuild>,
asset: AssetFile,
use_inline: bool,
}
impl Sass {
pub const TYPE_SASS: &'static str = "sass";
pub const TYPE_SCSS: &'static str = "scss";
pub async fn new(
cfg: Arc<RtcBuild>,
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="sass|scss" .../> element"#,
)?;
let mut path = PathBuf::new();
path.extend(href_attr.split('/'));
let asset = AssetFile::new(&html_dir, path).await?;
let use_inline = attrs.get(ATTR_INLINE).is_some();
Ok(Self {
id,
cfg,
asset,
use_inline,
})
}
#[tracing::instrument(level = "trace", skip(self))]
pub fn spawn(self) -> JoinHandle<Result<TrunkLinkPipelineOutput>> {
tokio::spawn(self.run())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn run(self) -> Result<TrunkLinkPipelineOutput> {
let version = self.cfg.tools.sass.as_deref();
let sass = tools::get(Application::Sass, version).await?;
let style = if self.cfg.release {
"compressed"
} else {
"expanded"
};
let path_str = dunce::simplified(&self.asset.path).display().to_string();
let file_name = format!("{}.css", &self.asset.file_stem.to_string_lossy());
let file_path = dunce::simplified(&self.cfg.staging_dist.join(&file_name))
.display()
.to_string();
let args = &["--no-source-map", "-s", style, &path_str, &file_path];
let rel_path = crate::common::strip_prefix(&self.asset.path);
tracing::info!(path = ?rel_path, "compiling sass/scss");
common::run_command(Application::Sass.name(), &sass, args).await?;
let css = fs::read_to_string(&file_path).await?;
fs::remove_file(&file_path).await?;
let css_ref = if self.use_inline {
CssRef::Inline(css)
} else {
let hash = seahash::hash(css.as_bytes());
let file_name = self
.cfg
.filehash
.then(|| format!("{}-{:x}.css", &self.asset.file_stem.to_string_lossy(), hash))
.unwrap_or(file_name);
let file_path = self.cfg.staging_dist.join(&file_name);
fs::write(&file_path, css)
.await
.context("error writing SASS pipeline output")?;
CssRef::File(file_name)
};
tracing::info!(path = ?rel_path, "finished compiling sass/scss");
Ok(TrunkLinkPipelineOutput::Sass(SassOutput {
cfg: self.cfg.clone(),
id: self.id,
css_ref,
}))
}
}
pub struct SassOutput {
pub cfg: Arc<RtcBuild>,
pub id: usize,
pub css_ref: CssRef,
}
pub enum CssRef {
Inline(String),
File(String),
}
impl SassOutput {
pub async fn finalize(self, dom: &mut Document) -> Result<()> {
let html = match self.css_ref {
CssRef::Inline(css) => format!(r#"<style type="text/css">{}</style>"#, css),
CssRef::File(file) => {
format!(
r#"<link rel="stylesheet" href="{base}{file}"/>"#,
base = &self.cfg.public_url,
)
}
};
dom.select(&super::trunk_id_selector(self.id))
.replace_with_html(html);
Ok(())
}
}