1use std::collections::HashMap;
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum ChangeKind {
6 Created,
7 Modified,
8 Deleted,
9 Renamed,
10}
11
12#[derive(Debug, Clone)]
13pub struct RawChange {
14 pub path: PathBuf,
15 pub kind: ChangeKind,
16}
17
18pub struct Debouncer {
21 pending: HashMap<PathBuf, ChangeKind>,
22}
23
24impl Debouncer {
25 pub fn new() -> Self {
26 Debouncer {
27 pending: HashMap::new(),
28 }
29 }
30
31 pub fn push(&mut self, change: RawChange) {
34 self.pending.insert(change.path, change.kind);
35 }
36
37 pub fn drain(&mut self) -> Vec<RawChange> {
39 let batch: Vec<RawChange> = self.pending
40 .drain()
41 .map(|(path, kind)| RawChange { path, kind })
42 .collect();
43 batch
44 }
45
46 pub fn has_pending(&self) -> bool {
48 !self.pending.is_empty()
49 }
50}
51
52impl Default for Debouncer {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use std::path::PathBuf;
62
63 #[test]
64 fn single_event_emitted_after_drain() {
65 let mut debouncer = Debouncer::new();
66 debouncer.push(RawChange {
67 path: PathBuf::from("src/main.rs"),
68 kind: ChangeKind::Modified,
69 });
70 let batch = debouncer.drain();
71 assert_eq!(batch.len(), 1);
72 assert_eq!(batch[0].path, PathBuf::from("src/main.rs"));
73 }
74
75 #[test]
76 fn duplicate_paths_coalesced() {
77 let mut debouncer = Debouncer::new();
78 debouncer.push(RawChange {
79 path: PathBuf::from("src/main.rs"),
80 kind: ChangeKind::Modified,
81 });
82 debouncer.push(RawChange {
83 path: PathBuf::from("src/main.rs"),
84 kind: ChangeKind::Modified,
85 });
86 let batch = debouncer.drain();
87 assert_eq!(batch.len(), 1);
88 }
89
90 #[test]
91 fn different_paths_preserved() {
92 let mut debouncer = Debouncer::new();
93 debouncer.push(RawChange {
94 path: PathBuf::from("a.rs"),
95 kind: ChangeKind::Modified,
96 });
97 debouncer.push(RawChange {
98 path: PathBuf::from("b.rs"),
99 kind: ChangeKind::Created,
100 });
101 let batch = debouncer.drain();
102 assert_eq!(batch.len(), 2);
103 }
104
105 #[test]
106 fn drain_clears_buffer() {
107 let mut debouncer = Debouncer::new();
108 debouncer.push(RawChange {
109 path: PathBuf::from("file.rs"),
110 kind: ChangeKind::Modified,
111 });
112 let _ = debouncer.drain();
113 let batch = debouncer.drain();
114 assert!(batch.is_empty());
115 }
116
117 #[test]
118 fn last_kind_wins_for_same_path() {
119 let mut debouncer = Debouncer::new();
120 debouncer.push(RawChange {
121 path: PathBuf::from("file.rs"),
122 kind: ChangeKind::Created,
123 });
124 debouncer.push(RawChange {
125 path: PathBuf::from("file.rs"),
126 kind: ChangeKind::Deleted,
127 });
128 let batch = debouncer.drain();
129 assert_eq!(batch.len(), 1);
130 assert_eq!(batch[0].kind, ChangeKind::Deleted);
131 }
132}