use crate::pipeline::{self, Options};
use notify::{RecursiveMode, Watcher};
use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::time::Duration;
const DEBOUNCE: Duration = Duration::from_millis(120);
pub fn run(options: Options) -> Result<(), String> {
let config_path = pipeline::ancestors(&options.source_root)
.into_iter()
.map(|directory| directory.join("luaux.toml"))
.find(|path| path.is_file());
let (sender, receiver) = mpsc::channel();
let mut watcher = notify::recommended_watcher(move |event| {
let _ = sender.send(event);
})
.map_err(|error| format!("watch: {error}"))?;
watcher
.watch(&options.source_root, RecursiveMode::Recursive)
.map_err(|error| format!("watch {}: {error}", options.source_root.display()))?;
if let Some(path) = &config_path {
let _ = watcher.watch(path, RecursiveMode::NonRecursive);
}
println!("luaux: watching {}", options.source_root.display());
match &config_path {
Some(path) => println!("luaux: watching {}", path.display()),
None => println!("luaux: no luaux.toml found — restart to pick one up"),
}
build_once(&options);
loop {
let Ok(first) = receiver.recv() else {
return Ok(());
};
let mut paths = event_paths(first);
while let Ok(next) = receiver.recv_timeout(DEBOUNCE) {
paths.extend(event_paths(next));
}
if !paths.iter().any(|path| relevant(path, &options)) {
continue;
}
println!();
build_once(&options);
}
}
fn build_once(options: &Options) {
let config = match pipeline::configure(&options.source_root) {
Ok(config) => config,
Err(error) => {
eprintln!("luaux: {error}");
return;
}
};
let report = pipeline::run(options, &config);
println!("luaux: {}", report.summary());
}
fn event_paths(event: notify::Result<notify::Event>) -> Vec<PathBuf> {
event.map(|event| event.paths).unwrap_or_default()
}
fn relevant(path: &Path, options: &Options) -> bool {
if let Some(out_root) = &options.out_root {
if under(path, out_root) {
return false;
}
}
match path.extension().and_then(|extension| extension.to_str()) {
Some("luaux") => true,
Some("luau") => options.out_root.is_some() && !is_generated(path),
_ => path.file_name().is_some_and(|name| name == "luaux.toml"),
}
}
fn is_generated(path: &Path) -> bool {
path.with_extension("luaux").is_file()
}
fn under(path: &Path, root: &Path) -> bool {
let path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
let root = std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf());
path.starts_with(root)
}