1use std::path::PathBuf;
2use std::sync::mpsc::channel;
3use std::time::{Duration, Instant};
4
5use dmc_diagnostic::Code;
6use duck_diagnostic::{DiagnosticEngine, print_all_smart};
7use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
8
9use crate::{Engine, engine::config::EngineConfig};
10
11#[derive(clap::Args)]
14pub struct DevCmd {
15 #[arg(long, default_value = "dmc.toml")]
16 pub config: PathBuf,
17 #[arg(short, long)]
18 pub strict: bool,
19 #[arg(long)]
20 pub clean: bool,
21 #[arg(long, default_value_t = 100)]
23 pub debounce: u64,
24}
25
26impl DevCmd {
27 pub fn run(self) -> std::io::Result<()> {
28 let mut cfg = EngineConfig::load(&self.config)?;
29 if self.strict {
30 cfg.strict = true;
31 }
32 if self.clean {
33 cfg.clean = true;
34 }
35
36 rebuild(&cfg, &self.config, "initial");
37
38 let (tx, rx) = channel();
39 let mut deb =
40 new_debouncer(Duration::from_millis(self.debounce), tx).map_err(|e| std::io::Error::other(e.to_string()))?;
41
42 let mut roots: Vec<PathBuf> = cfg.collections.iter().map(|c| c.base_dir.clone()).collect();
43 roots.push(self.config.clone());
44 for r in &roots {
45 if r.exists() {
46 deb.watcher().watch(r, RecursiveMode::Recursive).map_err(|e| std::io::Error::other(e.to_string()))?;
47 }
48 }
49
50 println!("watching {} root(s) - Ctrl-C to stop", roots.len());
51
52 while let Ok(events) = rx.recv() {
53 let Ok(events) = events else { continue };
54 let touched: Vec<PathBuf> = events.iter().map(|e| e.path.clone()).collect();
55 let cfg_canon = self.config.canonicalize().ok();
56 let config_changed = touched.iter().any(|p| p.canonicalize().ok() == cfg_canon);
57
58 if config_changed {
59 match EngineConfig::load(&self.config) {
60 Ok(mut next) => {
61 if self.strict {
62 next.strict = true;
63 }
64 if self.clean {
65 next.clean = true;
66 }
67 cfg = next;
68 },
69 Err(e) => {
70 eprintln!("config reload failed: {e}");
71 continue;
72 },
73 }
74 }
75
76 rebuild(&cfg, &self.config, if config_changed { "config" } else { "files" });
77 }
78
79 Ok(())
80 }
81}
82
83fn rebuild(cfg: &EngineConfig, config_path: &std::path::Path, trigger: &str) {
84 let mut diag_engine = DiagnosticEngine::<Code>::new();
85 let started = Instant::now();
86 let result = Engine::run(cfg, Some(config_path), &mut diag_engine);
87 let elapsed = started.elapsed();
88 print_all_smart(&diag_engine, None);
89 match result {
90 Ok(()) => println!("rebuilt ({trigger}) in {:?}", elapsed),
91 Err(e) => eprintln!("build error ({trigger}): {e}"),
92 }
93}