From 72e9f7e7bc36bf21014fd66a752ac770eb1ec86d Mon Sep 17 00:00:00 2001 From: Timur Sevimli Date: Thu, 9 Nov 2023 21:25:17 +0300 Subject: [PATCH] Add exclude files, exts, paths and add new events --- README.md | 11 ++++++- metawatch.js | 89 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index dfcc910..72f1aba 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,16 @@ ```js const metawatch = require('metawatch'); -const watcher = new metawatch.DirectoryWatcher({ timeout: 200 }); +const options = { + timeout: 200, + excludes: { + dirs: ['.git'], + files: ['package.json'], + exts: ['ts', 'md'], + }, +}; + +const watcher = new metawatch.DirectoryWatcher(options); watcher.watch('/home/marcus/Downloads'); watcher.watch('/home/marcus/Documents'); diff --git a/metawatch.js b/metawatch.js index ab6a560..46c397c 100644 --- a/metawatch.js +++ b/metawatch.js @@ -9,22 +9,38 @@ const WATCH_TIMEOUT = 5000; class DirectoryWatcher extends EventEmitter { constructor(options = {}) { super(); - this.watchers = new Map(); + const { dirs = [], files = [], exts = [] } = options.excludes || {}; const { timeout = WATCH_TIMEOUT } = options; this.timeout = timeout; + this.watchers = new Map(); + this.excludeExts = new Set(exts); + this.excludePaths = new Set(dirs); + this.excludeFiles = new Set(files); this.timer = null; this.queue = new Map(); } + isExcludedFile(filePath) { + const { excludeExts, excludeFiles } = this; + const { ext, base, name } = path.parse(filePath); + const extIsExclude = excludeExts.has(ext.slice(1)); + if (extIsExclude) return true; + return excludeFiles.has(name) || excludeFiles.has(base); + } + + isExcludedDir(dirPath) { + const { excludePaths } = this; + const dirName = path.basename(dirPath); + return excludePaths.has(dirName) || excludePaths.has(dirPath); + } + post(event, filePath) { if (this.timer) clearTimeout(this.timer); - this.queue.set(filePath, event); - if (this.timeout === 0) return void this.sendQueue(); + const events = this.queue.get(filePath); + if (events) events.add(event); + else this.queue.set(filePath, new Set(event)); this.timer = setTimeout(() => { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } + this.timer = null; this.sendQueue(); }, this.timeout); } @@ -34,49 +50,62 @@ class DirectoryWatcher extends EventEmitter { const queue = [...this.queue.entries()]; this.queue.clear(); this.emit('before', queue); - for (const [filePath, event] of queue) { - this.emit(event, filePath); + for (const [filePath, events] of queue) { + for (const event of events) { + this.emit(event, filePath); + } } this.emit('after', queue); } - watchDirectory(targetPath) { - if (this.watchers.get(targetPath)) return; - const watcher = fs.watch(targetPath, (event, fileName) => { - const target = targetPath.endsWith(path.sep + fileName); - const filePath = target ? targetPath : path.join(targetPath, fileName); + watchDirectory(dirPath) { + if (this.watchers.has(dirPath)) return; + const watcher = fs.watch(dirPath); + watcher.on('error', () => void this.unwatch(dirPath)); + watcher.on('change', (_, fileName) => { + const target = dirPath.endsWith(path.sep + fileName); + const filePath = target ? dirPath : path.join(dirPath, fileName); + if (this.isExcludedFile(filePath)) return; + this.post('*', filePath); fs.stat(filePath, (err, stats) => { if (err) { - this.unwatch(filePath); - return void this.post('delete', filePath); + const keys = [...this.watchers.keys()]; + const event = keys.includes(filePath) ? 'unlinkDir' : 'unlink'; + this.post(event, filePath); + return void this.unwatch(filePath); } if (stats.isDirectory()) this.watch(filePath); this.post('change', filePath); }); }); - this.watchers.set(targetPath, watcher); + this.watchers.set(dirPath, watcher); } - watch(targetPath) { - const watcher = this.watchers.get(targetPath); - if (watcher) return; + unwatch(filePath) { + const watcher = this.watchers.get(filePath); + if (!watcher) return; + watcher.close(); + this.watchers.delete(filePath); + } + + watch(targetPath = process.cwd()) { + if (this.isExcludedDir(targetPath)) return this; fs.readdir(targetPath, { withFileTypes: true }, (err, files) => { - if (err) return; + if (err) return this; for (const file of files) { - if (file.isDirectory()) { - const dirPath = path.join(targetPath, file.name); - this.watch(dirPath); - } + if (!file.isDirectory()) continue; + const dirPath = path.join(targetPath, file.name); + this.watch(dirPath); } this.watchDirectory(targetPath); + return this; }); + return this; } - unwatch(path) { - const watcher = this.watchers.get(path); - if (!watcher) return; - watcher.close(); - this.watchers.delete(path); + stop(filePath) { + if (filePath) return void this.unwatch(filePath); + for (const [filePath] of this.watchers) this.unwatch(filePath); } }