Skip to main content

stacksdapp_watcher/
lib.rs

1use anyhow::Result;
2use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
3use std::path::Path;
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5use tokio::sync::mpsc;
6use tokio::time::timeout;
7
8pub async fn watch_contracts(contracts_dir: &Path) -> Result<()> {
9    let (tx, mut rx) = mpsc::channel(32);
10    let mut watcher = RecommendedWatcher::new(
11        move |res| {
12            let _ = tx.blocking_send(res);
13        },
14        Config::default().with_poll_interval(Duration::from_millis(500)),
15    )?;
16
17    watcher.watch(contracts_dir, RecursiveMode::Recursive)?;
18
19    const DEBOUNCE_MS: u64 = 300;
20
21    while let Some(event) = rx.recv().await {
22        if let Ok(e) = event {
23            if e.paths
24                .iter()
25                .any(|p| p.extension().map(|x| x == "clar").unwrap_or(false))
26            {
27                // Debounce bursts of save/write events before regenerating.
28                while let Ok(Some(next_event)) =
29                    timeout(Duration::from_millis(DEBOUNCE_MS), rx.recv()).await
30                {
31                    if let Ok(next) = next_event {
32                        let is_clar_change = next
33                            .paths
34                            .iter()
35                            .any(|p| p.extension().map(|x| x == "clar").unwrap_or(false));
36                        if !is_clar_change {
37                            continue;
38                        }
39                    }
40                }
41
42                if let Err(e) = stacksdapp_codegen::generate_all_quiet().await {
43                    stacksdapp_shell::error(format!(
44                        "[{}] ✗ Contract bindings failed: {e}",
45                        timestamp_now()
46                    ));
47                } else {
48                    stacksdapp_shell::status(format!(
49                        "[{}] ✓ Contract bindings updated",
50                        timestamp_now()
51                    ));
52                }
53            }
54        }
55    }
56
57    Ok(())
58}
59
60fn timestamp_now() -> String {
61    let secs = SystemTime::now()
62        .duration_since(UNIX_EPOCH)
63        .map(|d| d.as_secs())
64        .unwrap_or(0);
65    let hours = (secs / 3600) % 24;
66    let mins = (secs / 60) % 60;
67    let s = secs % 60;
68    format!("{hours:02}:{mins:02}:{s:02}")
69}
70
71#[cfg(test)]
72mod tests {
73    use super::timestamp_now;
74    use stacksdapp_shell::{init, status, ColorMode, Format, Shell};
75
76    #[test]
77    fn watcher_status_respects_quiet_mode() {
78        init(Shell {
79            verbosity: 0,
80            quiet: true,
81            format: Format::Human,
82            color: ColorMode::Never,
83        });
84        status(format!("[{}] ✓ Contract bindings updated", timestamp_now()));
85    }
86}