diff --git a/src/build.rs b/src/build.rs index 5dec199..1bcb1a0 100644 --- a/src/build.rs +++ b/src/build.rs @@ -23,9 +23,9 @@ pub(crate) fn build_styles() { fs::write("dist/styles.css", css).unwrap(); } -pub(crate) fn build_content(ctx: &BuildContext, content: &[&Output]) { +pub(crate) fn build_content(ctx: &BuildContext, pending: &[&Output], hole: &[&Output]) { let now = std::time::Instant::now(); - render_all(ctx, content); + render_all(ctx, pending, hole); println!("Elapsed: {:.2?}", now.elapsed()); } @@ -72,8 +72,8 @@ fn copy_recursively(src: impl AsRef, dst: impl AsRef) -> io::Result< Ok(()) } -fn render_all(ctx: &BuildContext, items: &[&Output]) { - for item in items { +fn render_all(ctx: &BuildContext, pending: &[&Output], hole: &[&Output]) { + for item in pending { let file = match &item.kind { OutputKind::Asset(a) => Some(&a.meta.path), OutputKind::Virtual(_) => None, @@ -82,7 +82,7 @@ fn render_all(ctx: &BuildContext, items: &[&Output]) { item, Sack { ctx, - hole: items, + hole, path: &item.path, file, }, diff --git a/src/main.rs b/src/main.rs index edfa589..7c787aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,7 +105,7 @@ fn main() { }, ]; - let special = &[ + let special = vec![ Output { kind: Virtual::new(|sack| crate::html::map(sack).render().to_owned().into()).into(), path: "map/index.html".into(), @@ -159,11 +159,11 @@ fn main() { match args.mode { Mode::Build => { - build(&ctx, sources, special); + let _ = build(&ctx, sources, special); } Mode::Watch => { - build(&ctx, sources, special); - watch::watch(&ctx, sources).unwrap() + let state = build(&ctx, sources, special); + watch::watch(&ctx, sources, state).unwrap() } } } @@ -198,7 +198,7 @@ impl Source { } } -fn build(ctx: &BuildContext, sources: &[Source], special: &[Output]) { +fn build(ctx: &BuildContext, sources: &[Source], special: Vec) -> Vec { crate::build::clean_dist(); let sources: Vec<_> = sources @@ -208,13 +208,15 @@ fn build(ctx: &BuildContext, sources: &[Source], special: &[Output]) { .filter_map(Option::from) .collect(); - let assets: Vec<_> = sources.iter().chain(special).collect(); + let assets: Vec<_> = sources.iter().chain(special.iter()).collect(); - crate::build::build_content(ctx, &assets); + crate::build::build_content(ctx, &assets, &assets); crate::build::build_static(); crate::build::build_styles(); crate::build::build_pagefind(); crate::build::build_js(); + + sources.into_iter().chain(special).collect() } pub fn parse_frontmatter(raw: &str) -> (D, String) diff --git a/src/pipeline.rs b/src/pipeline.rs index c237c21..30cc63b 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -41,7 +41,7 @@ pub(crate) trait Content { /// Marks whether the item should be treated as a content page, converted into a standalone HTML /// page, or as a bundled asset. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) enum FileItemKind { /// Marks items converted to `index.html`. Index, @@ -50,7 +50,7 @@ pub(crate) enum FileItemKind { } /// Metadata for a single item consumed by SSG. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct FileItem { /// The kind of an item from disk. pub kind: FileItemKind, diff --git a/src/watch.rs b/src/watch.rs index 19ce121..4080aa4 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -1,13 +1,15 @@ +use std::collections::HashMap; use std::env; use std::io::Result; use std::net::{TcpListener, TcpStream}; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::rc::Rc; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use std::thread::JoinHandle; use std::time::Duration; -use camino::Utf8PathBuf; +use camino::{Utf8Path, Utf8PathBuf}; use notify::RecursiveMode; use notify_debouncer_mini::new_debouncer; use tungstenite::WebSocket; @@ -69,7 +71,7 @@ fn new_thread_ws_reload( (tx, thread) } -pub fn watch(ctx: &BuildContext, sources: &[Source]) -> Result<()> { +pub fn watch(ctx: &BuildContext, sources: &[Source], state: Vec) -> Result<()> { let root = env::current_dir().unwrap(); let server = TcpListener::bind("127.0.0.1:1337")?; let client = Arc::new(Mutex::new(vec![])); @@ -90,23 +92,46 @@ pub fn watch(ctx: &BuildContext, sources: &[Source]) -> Result<()> { let thread_i = new_thread_ws_incoming(server, client.clone()); let (tx_reload, thread_o) = new_thread_ws_reload(client.clone()); + let mut state: Vec> = state.into_iter().map(Rc::new).collect(); + while let Ok(events) = rx.recv().unwrap() { - let items = events + let paths: Vec = events .into_iter() .filter_map(|event| { Utf8PathBuf::from_path_buf(event.path) .ok() .and_then(|path| path.strip_prefix(&root).ok().map(ToOwned::to_owned)) - .and_then(|path| sources.iter().find_map(|s| s.get_maybe(&path))) }) - .filter_map(Option::from) - .collect::>(); + .collect(); - let items = items.iter().collect::>(); + let mut dirty = false; - build_content(ctx, &items); - build_styles(); - tx_reload.send(()).unwrap(); + { + let items: Vec> = paths + .iter() + .filter_map(|path| sources.iter().find_map(|s| s.get_maybe(path))) + .filter_map(Option::from) + .map(Rc::new) + .collect(); + + if !items.is_empty() { + let state_next = update_stream(&state, &items); + let abc: Vec<&Output> = items.iter().map(AsRef::as_ref).collect(); + let xyz: Vec<&Output> = state_next.iter().map(AsRef::as_ref).collect(); + build_content(ctx, &abc, &xyz); + state = state_next; + dirty = true; + } + } + + if paths.iter().any(|path| path.starts_with("styles")) { + build_styles(); + dirty = true; + } + + if dirty { + tx_reload.send(()).unwrap(); + } } thread_i.join().unwrap(); @@ -114,3 +139,13 @@ pub fn watch(ctx: &BuildContext, sources: &[Source]) -> Result<()> { Ok(()) } + +fn update_stream(old: &[Rc], new: &[Rc]) -> Vec> { + let mut map: HashMap<&Utf8Path, Rc> = HashMap::new(); + + for output in old.iter().chain(new) { + map.insert(&*output.path, output.clone()); + } + + map.into_values().collect() +}