Add sass rendering and embedding

main
trivernis 11 months ago
parent d3028e7952
commit 2a5062682b
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG Key ID: DFFFCC2C7A02DB45

73
Cargo.lock generated

@ -110,6 +110,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "arc-swap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]] [[package]]
name = "async-channel" name = "async-channel"
version = "1.8.0" version = "1.8.0"
@ -967,6 +973,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.6.2" version = "0.6.2"
@ -996,6 +1008,16 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -1006,6 +1028,39 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" version = "0.2.15"
@ -1323,6 +1378,23 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]]
name = "rsass"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a43695dd28122f6c684273de89796a56a98e02e9694b8ab57b160fdc6e6d69af"
dependencies = [
"arc-swap",
"fastrand",
"lazy_static",
"nom",
"num-bigint",
"num-integer",
"num-rational",
"num-traits",
"tracing",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -1940,6 +2012,7 @@ dependencies = [
"futures", "futures",
"globset", "globset",
"miette", "miette",
"rsass",
"serde", "serde",
"tera", "tera",
"tera-text-filters", "tera-text-filters",

@ -12,6 +12,7 @@ comrak = { version = "0.18.0", features = ["emojis"] }
futures = "0.3.28" futures = "0.3.28"
globset = { version = "0.4.10", features = ["serde", "serde1"] } globset = { version = "0.4.10", features = ["serde", "serde1"] }
miette = { version = "5.9.0", features = ["serde", "fancy"] } miette = { version = "5.9.0", features = ["serde", "fancy"] }
rsass = "0.27.0"
serde = { version = "1.0.164", features = ["derive"] } serde = { version = "1.0.164", features = ["derive"] }
tera = "1.19.0" tera = "1.19.0"
tera-text-filters = "1.0.0" tera-text-filters = "1.0.0"

@ -1,7 +1,4 @@
use std::{ use std::{path::Path, sync::Arc};
path::{Path, PathBuf},
sync::Arc,
};
use args::BuildArgs; use args::BuildArgs;
use clap::Parser; use clap::Parser;
@ -43,7 +40,7 @@ async fn build(args: &Args, _build_args: &BuildArgs, cfg: Config) -> Result<()>
.read_content() .read_content()
.await?; .await?;
ContentRenderer::new(ctx).render_all(dirs).await?; ContentRenderer::new(ctx).await?.render_all(dirs).await?;
Ok(()) Ok(())
} }
@ -53,7 +50,7 @@ fn build_context(base_path: &Path, config: &Config) -> Context {
let content_dir = base_path.join(folders.content.unwrap_or("content".into())); let content_dir = base_path.join(folders.content.unwrap_or("content".into()));
let template_dir = base_path.join(folders.templates.unwrap_or("templates".into())); let template_dir = base_path.join(folders.templates.unwrap_or("templates".into()));
let output_dir = base_path.join(folders.output.unwrap_or("dist".into())); let output_dir = base_path.join(folders.output.unwrap_or("dist".into()));
let stylesheet_dir = base_path.join(folders.stylesheets.unwrap_or("style".into())); let stylesheet_dir = base_path.join(folders.stylesheets.unwrap_or("styles".into()));
Context { Context {
content_dir, content_dir,

@ -3,25 +3,34 @@ use std::{path::PathBuf, sync::Arc};
use futures::future; use futures::future;
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use tera::{Context as TeraContext, Tera}; use tera::{Context as TeraContext, Tera};
use tokio::fs; use tokio::{fs, sync::Mutex};
use crate::{ use crate::{
context::Context, context::Context,
data::{load_page, FolderData}, data::{load_page, FolderData},
}; };
use self::style::{load_stylesheets, Stylesheets};
mod style; mod style;
// renders content using the given template folder // renders content using the given template folder
pub struct ContentRenderer { pub struct ContentRenderer {
template_glob: String, template_glob: String,
ctx: Arc<Context>, ctx: Arc<Context>,
styles: Arc<Mutex<Stylesheets>>,
} }
impl ContentRenderer { impl ContentRenderer {
pub fn new(ctx: Arc<Context>) -> Self { pub async fn new(ctx: Arc<Context>) -> Result<Self> {
let template_glob = format!("{}/**/*", ctx.template_dir.to_string_lossy()); let template_glob = format!("{}/**/*", ctx.template_dir.to_string_lossy());
Self { template_glob, ctx } let styles = load_stylesheets(&ctx.stylesheet_dir).await?;
Ok(Self {
template_glob,
ctx,
styles: Arc::new(Mutex::new(styles)),
})
} }
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
@ -75,20 +84,31 @@ impl ContentRenderer {
let page = load_page(&page_path).await?; let page = load_page(&page_path).await?;
let mut context = TeraContext::new(); let mut context = TeraContext::new();
let mut template_name = default_template; let mut template_name = default_template;
let mut style_name = template_name.to_owned();
match page { match page {
crate::data::Page::Data(data) => { crate::data::Page::Data(data) => {
if let Some(tmpl) = data.metadata.template { if let Some(tmpl) = data.metadata.template {
template_name = tmpl; template_name = tmpl.to_owned();
style_name = tmpl;
} }
context.insert("data", &data.data); context.insert("data", &data.data);
} }
crate::data::Page::Content(content) => context.insert("content", &content), crate::data::Page::Content(content) => context.insert("content", &content),
} }
{
let mut styles = self.styles.lock().await;
let style_embed = styles
.get_style_embed(&style_name, &self.ctx.output_dir)
.await?;
context.insert("style", &style_embed);
};
tracing::debug!("context = {context:?}"); tracing::debug!("context = {context:?}");
let html = tera.render(&template_name, &context).into_diagnostic()?; let html = tera
.render(&format!("{template_name}.html"), &context)
.into_diagnostic()?;
let rel_path = page_path let rel_path = page_path
.strip_prefix(&self.ctx.content_dir) .strip_prefix(&self.ctx.content_dir)
.into_diagnostic()?; .into_diagnostic()?;

@ -1,24 +1,37 @@
use std::{collections::HashMap, path::PathBuf}; use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use async_walkdir::WalkDir; use async_walkdir::WalkDir;
use futures::StreamExt; use futures::StreamExt;
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use rsass::output::Format;
use tokio::fs;
const DEFAULT_SHEET_NAME: &str = "style";
const EMBED_THRESHOLD: usize = 512;
pub struct Stylesheets { pub struct Stylesheets {
pub default_style: Option<PathBuf>, page_styles: HashMap<String, PathBuf>,
pub page_styles: HashMap<String, PathBuf>, processed_styles: HashMap<String, String>,
} }
#[tracing::instrument(level = "trace")]
pub async fn load_stylesheets(base_dir: &PathBuf) -> Result<Stylesheets> { pub async fn load_stylesheets(base_dir: &PathBuf) -> Result<Stylesheets> {
let mut entries = WalkDir::new(base_dir); let mut entries = WalkDir::new(base_dir);
let mut page_styles = HashMap::new(); let mut page_styles = HashMap::new();
let empty_path = PathBuf::new();
while let Some(res) = entries.next().await { while let Some(res) = entries.next().await {
match res { match res {
Ok(entry) => { Ok(entry) => {
let entry_path = entry.path(); let entry_path = entry.path();
if entry_path.is_file() { if entry_path.is_file() {
let rel_path = entry_path.strip_prefix(base_dir).into_diagnostic()?;
if let Some(file_name) = entry_path.file_stem() { if let Some(file_name) = entry_path.file_stem() {
let file_name = rel_path.parent().unwrap_or(&empty_path).join(file_name);
let file_name = file_name.to_string_lossy().into_owned(); let file_name = file_name.to_string_lossy().into_owned();
page_styles.insert(file_name, entry_path.to_owned()); page_styles.insert(file_name, entry_path.to_owned());
} }
@ -27,17 +40,64 @@ pub async fn load_stylesheets(base_dir: &PathBuf) -> Result<Stylesheets> {
Err(e) => return Err(e).into_diagnostic(), Err(e) => return Err(e).into_diagnostic(),
} }
} }
tracing::debug!("Styles {page_styles:?}");
let mut default_style = None;
for name in ["style", "default", "stylesheet", "index"] {
if let Some(style) = page_styles.remove(name) {
default_style = Some(style);
break;
}
}
Ok(Stylesheets { Ok(Stylesheets {
default_style,
page_styles, page_styles,
processed_styles: HashMap::new(),
}) })
} }
impl Stylesheets {
#[tracing::instrument(level = "trace", skip(self, out_dir))]
pub async fn get_style_embed(&mut self, name: &str, out_dir: &Path) -> Result<String> {
let mut styles: Vec<String> = Vec::with_capacity(2);
if let Some(default_style) = self
.get_processed_style(DEFAULT_SHEET_NAME, out_dir)
.await?
{
styles.push(default_style);
}
if let Some(style) = self.get_processed_style(name, out_dir).await? {
styles.push(style);
}
Ok(styles.join(""))
}
#[tracing::instrument(level = "trace", skip(self, out_dir))]
async fn get_processed_style(&mut self, name: &str, out_dir: &Path) -> Result<Option<String>> {
if let Some(processed) = self.processed_styles.get(name) {
Ok(Some(processed.to_owned()))
} else if let Some(source) = self.page_styles.get(name) {
let format = Format {
style: rsass::output::Style::Compressed,
..Default::default()
};
let style_contents = rsass::compile_scss_path(source, format).into_diagnostic()?;
let style_html = if style_contents.len() < EMBED_THRESHOLD {
let utf_contents = String::from_utf8(style_contents).into_diagnostic()?;
format!(r#"<style type="text/css">{utf_contents}</style>"#)
} else {
let output_path = out_dir.join(name).with_extension("css");
let parent = output_path.parent().unwrap();
if !parent.exists() {
fs::create_dir_all(parent).await.into_diagnostic()?;
}
fs::write(output_path, style_contents)
.await
.into_diagnostic()?;
format!(r#"<link rel="stylesheet" href="/{name}.css">"#)
};
self.processed_styles
.insert(name.to_owned(), style_html.to_owned());
Ok(Some(style_html))
} else {
Ok(None)
}
}
}

Loading…
Cancel
Save