SCSS compilation

This commit is contained in:
Shadowfacts 2025-01-01 23:31:50 -05:00
parent 60858bde24
commit 5d795c3084
8 changed files with 187 additions and 22 deletions

77
Cargo.lock generated
View File

@ -17,6 +17,18 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -26,6 +38,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -245,6 +263,12 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "codemap"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
[[package]]
name = "colorchoice"
version = "1.0.3"
@ -561,8 +585,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@ -595,11 +621,38 @@ dependencies = [
"walkdir",
]
[[package]]
name = "grass"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7a68216437ef68f0738e48d6c7bb9e6e6a92237e001b03d838314b068f33c94"
dependencies = [
"getrandom",
"grass_compiler",
]
[[package]]
name = "grass_compiler"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6"
dependencies = [
"codemap",
"indexmap",
"lasso",
"once_cell",
"phf",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "html5ever"
@ -889,6 +942,15 @@ dependencies = [
"libc",
]
[[package]]
name = "lasso"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb"
dependencies = [
"hashbrown",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -1156,6 +1218,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros",
"phf_shared 0.11.2",
]
@ -1189,6 +1252,19 @@ dependencies = [
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
@ -1821,6 +1897,7 @@ dependencies = [
"debounced",
"env_logger",
"futures",
"grass",
"html5ever",
"log",
"markup5ever_rcdom",

View File

@ -23,6 +23,7 @@ compute_graph = { path = "crates/compute_graph" }
debounced = "0.2.0"
env_logger = "0.11.6"
futures = "0.3.31"
grass = { version = "0.13.4", default-features = false }
html5ever = "0.27.0"
log = "0.4.22"
markup5ever_rcdom = "0.3.0"

5
site_test/css/main.scss Normal file
View File

@ -0,0 +1,5 @@
.foo {
.bar {
color: red;
}
}

90
src/generator/css.rs Normal file
View File

@ -0,0 +1,90 @@
use std::{cell::RefCell, collections::HashSet, io::Write, path::PathBuf, rc::Rc};
use compute_graph::{
InvalidationSignal,
builder::GraphBuilder,
rule::{Input, InputVisitable, Rule},
synchronicity::Asynchronous,
};
use grass::{Fs, Options, OutputStyle};
use log::error;
use super::{
FileWatcher,
util::{content_path, output_writer},
};
pub fn make_graph(
builder: &mut GraphBuilder<(), Asynchronous>,
watcher: Rc<RefCell<FileWatcher>>,
) -> Input<()> {
let invalidate_css_box = Rc::new(RefCell::new(None));
let (css, invalidate_css) = builder.add_invalidatable_rule(CompileScss {
watcher,
watched: HashSet::new(),
invalidate: Rc::clone(&invalidate_css_box),
});
invalidate_css_box.replace(Some(invalidate_css));
css
}
#[derive(InputVisitable)]
struct CompileScss {
watcher: Rc<RefCell<FileWatcher>>,
watched: HashSet<PathBuf>,
invalidate: Rc<RefCell<Option<InvalidationSignal>>>,
}
impl Rule for CompileScss {
type Output = ();
fn evaluate(&mut self) -> Self::Output {
let read_files = RefCell::new(vec![]);
let fs = TrackingFs(&read_files);
let style = if cfg!(debug_assertions) {
OutputStyle::Expanded
} else {
OutputStyle::Compressed
};
let options = Options::default().fs(&fs).style(style);
let result = grass::from_path(content_path("css/main.scss"), &options);
let mut watcher = self.watcher.borrow_mut();
for file in read_files.take() {
if !self.watched.contains(&file) {
self.watched.insert(file.clone());
let signal = self.invalidate.borrow().clone().unwrap();
watcher.watch(file, move || signal.invalidate());
}
}
match result {
Ok(s) => {
output_writer("css/main.css")
.expect("css writer")
.write_all(s.as_bytes())
.expect("writing css");
}
Err(e) => {
error!("Error compiling sass: {:?}", e);
}
}
}
}
#[derive(Debug)]
struct TrackingFs<'a>(&'a RefCell<Vec<PathBuf>>);
impl<'a> Fs for TrackingFs<'a> {
fn is_file(&self, path: &std::path::Path) -> bool {
path.is_file()
}
fn is_dir(&self, path: &std::path::Path) -> bool {
path.is_dir()
}
fn read(&self, path: &std::path::Path) -> std::io::Result<Vec<u8>> {
self.0.borrow_mut().push(path.to_owned());
std::fs::read(path)
}
fn canonicalize(&self, path: &std::path::Path) -> std::io::Result<PathBuf> {
std::fs::canonicalize(path)
}
}

View File

@ -18,6 +18,7 @@ pub fn parse<'a>(s: &'a str) -> impl Iterator<Item = Event<'a>> {
options.insert(Options::ENABLE_STRIKETHROUGH);
options.insert(Options::ENABLE_SMART_PUNCTUATION);
let parser = Parser::new_ext(s, options);
// TODO: revisit which of these stages are necessary, remove unused (and url crate dep)
let heading_anchors = heading_anchors::new(parser);
let link_decorations = link_decorations::new(heading_anchors);
// note backrefs need to come before defs, because the defs stage replaces the

View File

@ -1,4 +1,5 @@
mod archive;
mod css;
mod markdown;
mod posts;
mod tags;
@ -41,30 +42,23 @@ fn make_graph(watcher: Rc<RefCell<FileWatcher>>) -> anyhow::Result<AsyncGraph<()
&mut *watcher.borrow_mut(),
);
let tag_output = tags::make_graph(
let tags = tags::make_graph(
&mut builder,
posts,
default_template,
&mut *watcher.borrow_mut(),
);
let css = css::make_graph(&mut builder, Rc::clone(&watcher));
let post_metadatas_voided = builder.add_rule(MapToVoid(post_metadatas));
let output = Combine::make(&mut builder, &[
void_outputs,
archive,
tag_output,
tags,
css,
post_metadatas_voided,
]);
builder.set_existing_output(output);
Ok(builder.build()?)
}
// #[derive(InputVisitable)]
// struct Output {
// archive: Input<()>,
// posts: Input<()>,
// }
// impl Rule for Output {
// type Output = ();
// fn evaluate(&mut self) -> Self::Output {}
// }

View File

@ -1,6 +1,8 @@
use std::{
cell::RefCell,
collections::HashMap,
path::{Path, PathBuf},
rc::Rc,
};
use notify::{EventHandler, Watcher};
@ -10,14 +12,12 @@ use crate::generator::util::content_base_path;
pub struct FileWatcher {
handlers: HashMap<PathBuf, Box<dyn Fn() -> ()>>,
watcher: Option<notify::RecommendedWatcher>,
}
impl FileWatcher {
pub fn new() -> Self {
Self {
handlers: HashMap::new(),
watcher: None,
}
}
@ -35,20 +35,18 @@ impl FileWatcher {
}
}
pub async fn start(&mut self) -> anyhow::Result<()> {
assert!(self.watcher.is_none());
pub async fn start(self_: Rc<RefCell<Self>>) -> anyhow::Result<()> {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<notify::Result<notify::Event>>();
let mut watcher = notify::recommended_watcher(AsyncEventHandler(tx))?;
watcher.watch(&content_base_path(), notify::RecursiveMode::Recursive)?;
self.watcher = Some(watcher);
let mut absolute_content_parent = content_base_path().canonicalize()?;
absolute_content_parent.pop();
while let Some(result) = rx.recv().await {
if let Ok(ev) = result {
self.handle_event(ev, &absolute_content_parent);
self_.borrow().handle_event(ev, &absolute_content_parent);
}
}

View File

@ -48,15 +48,14 @@ async fn main() {
if matches.contains_id("watch") {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<()>();
let mut watcher = watcher.borrow_mut();
watcher.watch(content_base_path(), move || {
watcher.borrow_mut().watch(content_base_path(), move || {
tx.send(()).expect("sending regenerate signal");
});
let mut debounced =
debounced(UnboundedReceiverStream::new(rx), Duration::from_millis(500));
debounced(UnboundedReceiverStream::new(rx), Duration::from_millis(100));
let watch = watcher.start().fuse();
let watch = FileWatcher::start(watcher).fuse();
let regenerate = async move {
while let Some(_) = debounced.next().await {