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",
]
[[package]]
name = "arc-swap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]]
name = "async-channel"
version = "1.8.0"
@ -967,6 +973,12 @@ dependencies = [
"syn",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
@ -996,6 +1008,16 @@ dependencies = [
"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]]
name = "nu-ansi-term"
version = "0.46.0"
@ -1006,6 +1028,39 @@ dependencies = [
"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]]
name = "num-traits"
version = "0.2.15"
@ -1323,6 +1378,23 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "rustc-demangle"
version = "0.1.23"
@ -1940,6 +2012,7 @@ dependencies = [
"futures",
"globset",
"miette",
"rsass",
"serde",
"tera",
"tera-text-filters",

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

@ -1,7 +1,4 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use std::{path::Path, sync::Arc};
use args::BuildArgs;
use clap::Parser;
@ -43,7 +40,7 @@ async fn build(args: &Args, _build_args: &BuildArgs, cfg: Config) -> Result<()>
.read_content()
.await?;
ContentRenderer::new(ctx).render_all(dirs).await?;
ContentRenderer::new(ctx).await?.render_all(dirs).await?;
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 template_dir = base_path.join(folders.templates.unwrap_or("templates".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 {
content_dir,

@ -3,25 +3,34 @@ use std::{path::PathBuf, sync::Arc};
use futures::future;
use miette::{IntoDiagnostic, Result};
use tera::{Context as TeraContext, Tera};
use tokio::fs;
use tokio::{fs, sync::Mutex};
use crate::{
context::Context,
data::{load_page, FolderData},
};
use self::style::{load_stylesheets, Stylesheets};
mod style;
// renders content using the given template folder
pub struct ContentRenderer {
template_glob: String,
ctx: Arc<Context>,
styles: Arc<Mutex<Stylesheets>>,
}
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());
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)]
@ -75,20 +84,31 @@ impl ContentRenderer {
let page = load_page(&page_path).await?;
let mut context = TeraContext::new();
let mut template_name = default_template;
let mut style_name = template_name.to_owned();
match page {
crate::data::Page::Data(data) => {
if let Some(tmpl) = data.metadata.template {
template_name = tmpl;
template_name = tmpl.to_owned();
style_name = tmpl;
}
context.insert("data", &data.data);
}
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:?}");
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
.strip_prefix(&self.ctx.content_dir)
.into_diagnostic()?;

@ -1,24 +1,37 @@
use std::{collections::HashMap, path::PathBuf};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use async_walkdir::WalkDir;
use futures::StreamExt;
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 default_style: Option<PathBuf>,
pub page_styles: HashMap<String, PathBuf>,
page_styles: HashMap<String, PathBuf>,
processed_styles: HashMap<String, String>,
}
#[tracing::instrument(level = "trace")]
pub async fn load_stylesheets(base_dir: &PathBuf) -> Result<Stylesheets> {
let mut entries = WalkDir::new(base_dir);
let mut page_styles = HashMap::new();
let empty_path = PathBuf::new();
while let Some(res) = entries.next().await {
match res {
Ok(entry) => {
let entry_path = entry.path();
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() {
let file_name = rel_path.parent().unwrap_or(&empty_path).join(file_name);
let file_name = file_name.to_string_lossy().into_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(),
}
}
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;
}
}
tracing::debug!("Styles {page_styles:?}");
Ok(Stylesheets {
default_style,
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