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