oxedyne/fe2o3/fe2o3_austenite/src/watch.rs
2.1 KiB, 1 run
created by r1870400018:37573, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Poll-based recompile for the `austenite --watch` mode. |
| 2 | //! |
| 3 | //! There is no inotify on this fleet, so the watch samples a caller-supplied set of files on a fixed |
| 4 | //! interval and rebuilds whenever any of them changes. The file set is recomputed each tick from a |
| 5 | //! closure, so a chapter added to a book's `#include` list (or an asset dropped into its tree) is |
| 6 | //! picked up without restarting the watch. A file appearing or disappearing counts as a change, since |
| 7 | //! the snapshot keys on the paths that currently exist. |
| 8 | |
| 9 | use oxedyne_fe2o3_core::prelude::*; |
| 10 | |
| 11 | use std::collections::BTreeMap; |
| 12 | use std::path::PathBuf; |
| 13 | use std::time::{ |
| 14 | Duration, |
| 15 | SystemTime, |
| 16 | }; |
| 17 | |
| 18 | /// Runs the poll loop: build once, then on every tick recompute the watched set via `files`, sample |
| 19 | /// their modification times, and call `build` whenever the sample differs from the last one -- an |
| 20 | /// mtime moved, or a watched file appeared or vanished. A build that fails returns its error, which is |
| 21 | /// printed here, and the loop carries on, so a transient compile error never stops the watch. Under |
| 22 | /// normal use this never returns; it ends only on an interrupt. |
| 23 | pub fn run<F, G>(mut files: G, mut build: F, interval: Duration) -> Outcome<()> |
| 24 | where |
| 25 | F: FnMut() -> Outcome<()>, |
| 26 | G: FnMut() -> Vec<PathBuf>, |
| 27 | { |
| 28 | // Build once at the start so the watch shows a result immediately rather than on the first edit. |
| 29 | if let Err(e) = build() { |
| 30 | eprintln!("[austenite] {}", e); |
| 31 | } |
| 32 | let mut prev = snapshot(&files()); |
| 33 | loop { |
| 34 | std::thread::sleep(interval); |
| 35 | let now = snapshot(&files()); |
| 36 | if now != prev { |
| 37 | prev = now; |
| 38 | if let Err(e) = build() { |
| 39 | eprintln!("[austenite] {}", e); |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | /// Maps each path that currently exists and can be stat'd to its last-modified time. A path that |
| 46 | /// cannot be read is simply absent from the map, so its later appearance -- or the disappearance of one |
| 47 | /// present before -- changes the snapshot and triggers a rebuild. |
| 48 | fn snapshot(paths: &[PathBuf]) -> BTreeMap<PathBuf, SystemTime> { |
| 49 | let mut m = BTreeMap::new(); |
| 50 | for p in paths { |
| 51 | if let Ok(meta) = std::fs::metadata(p) { |
| 52 | if let Ok(t) = meta.modified() { |
| 53 | m.insert(p.clone(), t); |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | m |
| 58 | } |