mermaid_cli/utils/
file_watcher.rs1use anyhow::Result;
2use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
3use std::path::{Path, PathBuf};
4use std::sync::mpsc::{self, Receiver};
5
6#[derive(Debug, Clone)]
8pub enum FileEvent {
9 Created(Vec<PathBuf>),
10 Modified(Vec<PathBuf>),
11 Deleted(Vec<PathBuf>),
12}
13
14pub struct FileSystemWatcher {
16 _watcher: RecommendedWatcher,
17 rx: Receiver<Result<Event, notify::Error>>,
18}
19
20impl FileSystemWatcher {
21 pub fn new(path: &Path) -> Result<Self> {
23 let (tx, rx) = mpsc::channel();
24
25 let mut watcher = notify::recommended_watcher(move |event| {
26 let _ = tx.send(event);
27 })?;
28
29 watcher.watch(path, RecursiveMode::Recursive)?;
31
32 Ok(Self {
33 _watcher: watcher,
34 rx,
35 })
36 }
37
38 pub fn check_events(&self) -> Vec<FileEvent> {
40 let mut events = Vec::new();
41
42 while let Ok(Ok(event)) = self.rx.try_recv() {
44 match event.kind {
45 EventKind::Create(_) => {
46 if !event.paths.is_empty() {
47 events.push(FileEvent::Created(event.paths));
48 }
49 },
50 EventKind::Modify(modify_kind) => {
51 use notify::event::ModifyKind;
53 match modify_kind {
54 ModifyKind::Data(_) | ModifyKind::Any => {
55 if !event.paths.is_empty() {
56 events.push(FileEvent::Modified(event.paths));
57 }
58 },
59 _ => {}, }
61 },
62 EventKind::Remove(_) => {
63 if !event.paths.is_empty() {
64 events.push(FileEvent::Deleted(event.paths));
65 }
66 },
67 _ => {}, }
69 }
70
71 events
72 }
73
74 pub fn should_ignore_path(path: &Path) -> bool {
76 if let Some(name) = path.file_name() {
78 if let Some(name_str) = name.to_str() {
79 if name_str.starts_with('.') {
80 return true;
81 }
82 }
83 }
84
85 if let Some(parent) = path.parent() {
87 if let Some(parent_name) = parent.file_name() {
88 if let Some(parent_str) = parent_name.to_str() {
89 match parent_str {
90 "target" | "node_modules" | "__pycache__" | ".git" | "dist" | "build"
91 | ".venv" | "venv" => return true,
92 _ => {},
93 }
94 }
95 }
96 }
97
98 if let Some(ext) = path.extension() {
100 if let Some(ext_str) = ext.to_str() {
101 match ext_str {
102 "txt" | "md" | "rs" | "toml" | "yaml" | "yml" | "json" | "js" | "ts"
104 | "jsx" | "tsx" | "py" | "go" | "java" | "c" | "cpp" | "h" | "hpp" | "sh"
105 | "bash" | "zsh" | "fish" | "html" | "css" | "scss" | "xml" | "vue"
106 | "svelte" => false,
107 _ => true,
109 }
110 } else {
111 true }
113 } else {
114 false }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use std::fs;
123 use tempfile::TempDir;
124
125 #[test]
126 fn test_should_ignore_path() {
127 assert!(FileSystemWatcher::should_ignore_path(Path::new(
128 ".gitignore"
129 )));
130 assert!(FileSystemWatcher::should_ignore_path(Path::new(
131 "node_modules/package.json"
132 )));
133 assert!(FileSystemWatcher::should_ignore_path(Path::new(
134 "image.png"
135 )));
136
137 assert!(!FileSystemWatcher::should_ignore_path(Path::new("main.rs")));
138 assert!(!FileSystemWatcher::should_ignore_path(Path::new(
139 "README.md"
140 )));
141 assert!(!FileSystemWatcher::should_ignore_path(Path::new(
142 "config.toml"
143 )));
144 }
145
146 #[tokio::test]
147 async fn test_file_watcher_events() {
148 let temp_dir = TempDir::new().unwrap();
149 let watcher = FileSystemWatcher::new(temp_dir.path()).unwrap();
150
151 let test_file = temp_dir.path().join("test.txt");
153 fs::write(&test_file, "hello").unwrap();
154
155 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
157
158 let events = watcher.check_events();
159 assert!(!events.is_empty(), "Should have detected file creation");
160 }
161}