UNPKG

globwatcher

Version:

watch a set of files for changes (including create/delete) by glob patterns

625 lines (540 loc) 18.4 kB
// Generated by CoffeeScript 1.6.2 (function() { var FileWatcher, GlobWatcher, Q, WatchMap, events, folderMatchesMinimatchPrefix, fs, glob, makePromise, minimatch, path, util, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __slice = [].slice; events = require('events'); fs = require('fs'); glob = require('glob'); minimatch = require('minimatch'); path = require('path'); Q = require('q'); util = require('util'); makePromise = require("./make_promise").makePromise; FileWatcher = require("./filewatcher").FileWatcher; folderMatchesMinimatchPrefix = function(folderSegments, minimatchSet) { var i, miniSegment, segment, _i, _len; for (i = _i = 0, _len = folderSegments.length; _i < _len; i = ++_i) { segment = folderSegments[i]; if (i >= minimatchSet.length) { return false; } miniSegment = minimatchSet[i]; if (miniSegment === minimatch.GLOBSTAR) { return true; } if (typeof miniSegment === "string") { if (miniSegment !== segment) { return false; } } else { if (!miniSegment.test(segment)) { return false; } } } return true; }; exports.folderMatchesMinimatchPrefix = folderMatchesMinimatchPrefix; WatchMap = (function() { function WatchMap(map) { this.map = map != null ? map : {}; } WatchMap.prototype.clear = function() { return this.map = {}; }; WatchMap.prototype.watchFolder = function(folderName) { var _base; return (_base = this.map)[folderName] || (_base[folderName] = {}); }; WatchMap.prototype.unwatchFolder = function(folderName) { return delete this.map[folderName]; }; WatchMap.prototype.watchFile = function(filename, parent) { var _base; return ((_base = this.map)[parent] || (_base[parent] = {}))[filename] = true; }; WatchMap.prototype.unwatchFile = function(filename, parent) { return delete this.map[parent][filename]; }; WatchMap.prototype.getFolders = function() { return Object.keys(this.map); }; WatchMap.prototype.getFilenames = function(folderName) { return Object.keys(this.map[folderName] || {}); }; WatchMap.prototype.getAllFilenames = function() { var folder, rv, _i, _len, _ref; rv = []; _ref = this.getFolders(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { folder = _ref[_i]; rv = rv.concat(this.getFilenames(folder)); } return rv; }; WatchMap.prototype.getNestedFolders = function(folderName) { var f, _i, _len, _ref, _results; _ref = this.getFolders(); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { f = _ref[_i]; if (f.slice(0, folderName.length) === folderName) { _results.push(f); } } return _results; }; WatchMap.prototype.watchingFolder = function(folderName) { return this.map[folderName] != null; }; WatchMap.prototype.watchingFile = function(filename, parent) { var _ref; if (parent == null) { parent = path.dirname(filename); } return ((_ref = this.map[parent]) != null ? _ref[filename] : void 0) != null; }; WatchMap.prototype.toDebug = function() { var filename, folder, out, _i, _j, _len, _len1, _ref, _ref1; out = []; _ref = Object.keys(this.map).sort(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { folder = _ref[_i]; out.push(folder); _ref1 = Object.keys(this.map[folder]).sort(); for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { filename = _ref1[_j]; out.push(" `- " + filename); } } return out.join("\n") + "\n"; }; return WatchMap; })(); exports.globwatcher = function(pattern, options) { return new GlobWatcher(pattern, options); }; GlobWatcher = (function(_super) { __extends(GlobWatcher, _super); function GlobWatcher(patterns, options) { if (options == null) { options = {}; } this.closed = false; this.cwd = options.cwd || process.cwd(); this.debounceInterval = options.debounceInterval || 10; this.interval = options.interval || 250; this.debug = options.debug || (function() {}); this.persistent = options.persistent || false; this.watchMap = new WatchMap; this.fileWatcher = new FileWatcher(options); this.watchers = {}; this.patterns = []; this.minimatchSets = []; this.checkQueue = {}; if (typeof patterns === "string") { patterns = [patterns]; } this.originalPatterns = patterns; if (options.snapshot) { this.restoreFrom(options.snapshot, patterns); } else { this.add.apply(this, patterns); } } GlobWatcher.prototype.add = function() { var p, patterns, _this = this; patterns = 1 <= arguments.length ? __slice.call(arguments, 0) : []; this.debug("add: " + (util.inspect(patterns))); this.originalPatterns = this.originalPatterns.concat(patterns); this.addPatterns(patterns); return this.ready = Q.all((function() { var _i, _len, _ref, _results, _this = this; _ref = this.patterns; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { p = _ref[_i]; _results.push(makePromise(glob)(p, { nonegate: true }).then(function(files) { var filename, _j, _len1, _results1; _results1 = []; for (_j = 0, _len1 = files.length; _j < _len1; _j++) { filename = files[_j]; _results1.push(_this.addWatch(filename)); } return _results1; })); } return _results; }).call(this)).then(function() { _this.stopWatches(); _this.startWatches(); return Q.delay(_this.debounceInterval); }).then(function() { _this.debug("add complete: " + (util.inspect(patterns))); return _this; }); }; GlobWatcher.prototype.close = function() { this.debug("close"); this.stopWatches(); this.watchMap.clear(); return this.closed = true; }; GlobWatcher.prototype.check = function() { var folders, _this = this; this.debug("-> check"); folders = Object.keys(this.watchers).map(function(folderName) { return _this.folderChanged(folderName); }); return Q.all([this.fileWatcher.check()].concat(folders)).then(function() { return _this.debug("<- check"); }); }; GlobWatcher.prototype.currentSet = function() { return this.watchMap.getAllFilenames(); }; GlobWatcher.prototype.snapshot = function() { var filename, state, w, _i, _len, _ref; state = {}; _ref = this.watchMap.getAllFilenames(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { filename = _ref[_i]; w = this.fileWatcher.watchFor(filename); if (w != null) { state[filename] = { mtime: w.mtime, size: w.size }; } } return state; }; GlobWatcher.prototype.restoreFrom = function(state, patterns) { var filename, folderName, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _this = this; this.addPatterns(patterns); _ref = Object.keys(state); for (_i = 0, _len = _ref.length; _i < _len; _i++) { filename = _ref[_i]; folderName = path.dirname(filename); if (folderName !== "/") { folderName += "/"; } this.watchMap.watchFile(filename, folderName); } _ref1 = this.watchMap.getFolders(); for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { folderName = _ref1[_j]; this.watchFolder(folderName); } _ref2 = Object.keys(state); for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { filename = _ref2[_k]; this.watchFile(filename, state[filename].mtime, state[filename].size); } return this.ready = Q.delay(this.debounceInterval).then(function() { _this.debug("restore complete: " + (util.inspect(patterns))); return _this.check(); }).then(function() { return _this; }); }; GlobWatcher.prototype.addPatterns = function(patterns) { var p, set, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _results; for (_i = 0, _len = patterns.length; _i < _len; _i++) { p = patterns[_i]; p = this.absolutePath(p); if (this.patterns.indexOf(p) < 0) { this.patterns.push(p); } } this.minimatchSets = []; _ref = this.patterns; for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { p = _ref[_j]; this.minimatchSets = this.minimatchSets.concat(new minimatch.Minimatch(p, { nonegate: true }).set); } _ref1 = this.minimatchSets; _results = []; for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { set = _ref1[_k]; _results.push(this.watchPrefix(set)); } return _results; }; GlobWatcher.prototype.watchPrefix = function(minimatchSet) { var index, parent, prefix; index = 0; while (index < minimatchSet.length && typeof minimatchSet[index] === "string") { index += 1; } prefix = path.join.apply(path, ["/"].concat(__slice.call(minimatchSet.slice(0, index)))); parent = path.dirname(prefix); while (!fs.existsSync(prefix) && parent !== path.dirname(parent)) { prefix = path.dirname(prefix); parent = path.dirname(parent); } if (fs.existsSync(prefix)) { return this.watchMap.watchFolder(prefix + "/"); } }; GlobWatcher.prototype.absolutePath = function(p) { if (p[0] === '/') { return p; } else { return path.join(this.cwd, p); } }; GlobWatcher.prototype.isMatch = function(filename) { var p, _i, _len, _ref; _ref = this.patterns; for (_i = 0, _len = _ref.length; _i < _len; _i++) { p = _ref[_i]; if (minimatch(filename, p, { nonegate: true })) { return true; } } return false; }; GlobWatcher.prototype.addWatch = function(filename) { var e, isdir, parent; isdir = (function() { try { return fs.statSync(filename).isDirectory(); } catch (_error) { e = _error; return false; } })(); if (isdir) { filename += "/"; return this.watchMap.watchFolder(filename); } else { parent = path.dirname(filename); if (parent !== "/") { parent += "/"; } return this.watchMap.watchFile(filename, parent); } }; GlobWatcher.prototype.stopWatches = function() { var filename, folderName, watcher, _i, _j, _len, _len1, _ref, _ref1, _ref2; _ref = this.watchers; for (filename in _ref) { watcher = _ref[filename]; watcher.close(); } _ref1 = this.watchMap.getFolders(); for (_i = 0, _len = _ref1.length; _i < _len; _i++) { folderName = _ref1[_i]; _ref2 = this.watchMap.getFilenames(folderName); for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { filename = _ref2[_j]; this.fileWatcher.unwatch(filename); } } return this.watchers = {}; }; GlobWatcher.prototype.startWatches = function() { var filename, folderName, _i, _len, _ref, _results; _ref = this.watchMap.getFolders(); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { folderName = _ref[_i]; this.watchFolder(folderName); _results.push((function() { var _j, _len1, _ref1, _results1; _ref1 = this.watchMap.getFilenames(folderName); _results1 = []; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { filename = _ref1[_j]; if (filename[filename.length - 1] !== "/") { _results1.push(this.watchFile(filename)); } else { _results1.push(void 0); } } return _results1; }).call(this)); } return _results; }; GlobWatcher.prototype.watchFolder = function(folderName) { var e, _this = this; this.debug("watch: " + folderName); try { return this.watchers[folderName] = fs.watch(folderName, { persistent: this.persistent }, function(event) { _this.debug("watch event: " + folderName); _this.checkQueue[folderName] = true; return setTimeout((function() { return _this.scanQueue(); }), _this.debounceInterval); }); } catch (_error) { e = _error; } }; GlobWatcher.prototype.scanQueue = function() { var f, folders, _i, _len, _results; folders = Object.keys(this.checkQueue); this.checkQueue = {}; _results = []; for (_i = 0, _len = folders.length; _i < _len; _i++) { f = folders[_i]; _results.push(this.folderChanged(f)); } return _results; }; GlobWatcher.prototype.watchFile = function(filename, mtime, size) { var _this = this; if (mtime == null) { mtime = null; } if (size == null) { size = null; } this.debug("watchFile: " + filename); return this.fileWatcher.watch(filename, mtime, size).on('changed', function() { _this.debug("watchFile event: " + filename); return _this.emit('changed', filename); }); }; GlobWatcher.prototype.folderChanged = function(folderName) { var _this = this; this.debug("-> check folder: " + folderName); if (this.closed) { return; } return makePromise(fs.readdir)(folderName).fail(function(error) { _this.debug(" ERR: " + error); return []; }).then(function(current) { var f, previous, _i, _len, _ref; if (_this.closed) { return; } current = current.map(function(filename) { var e; filename = path.join(folderName, filename); try { if (fs.statSync(filename).isDirectory()) { filename += "/"; } } catch (_error) { e = _error; } return filename; }); previous = _this.watchMap.getFilenames(folderName); _ref = previous.filter(function(x) { return current.indexOf(x) < 0; }); for (_i = 0, _len = _ref.length; _i < _len; _i++) { f = _ref[_i]; if (f[f.length - 1] === '/') { _this.folderDeleted(f); } else { _this.fileDeleted(f); } } return Q.all((function() { var _j, _len1, _ref1, _results; _ref1 = current.filter(function(x) { return previous.indexOf(x) < 0; }); _results = []; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { f = _ref1[_j]; if (f[f.length - 1] === '/') { _results.push(this.folderAdded(f)); } else { _results.push(this.fileAdded(f, folderName)); } } return _results; }).call(_this)); }).then(function() { return _this.debug("<- check folder: " + folderName); }); }; GlobWatcher.prototype.fileDeleted = function(filename) { var parent; this.debug("file deleted: " + filename); parent = path.dirname(filename); if (parent !== "/") { parent += "/"; } if (this.watchMap.watchingFile(filename, parent)) { fs.unwatchFile(filename); this.watchMap.unwatchFile(filename, parent); } return this.emit('deleted', filename); }; GlobWatcher.prototype.folderDeleted = function(folderName) { var filename, folder, _i, _j, _len, _len1, _ref, _ref1, _results; this.debug("folder deleted: " + folderName); _ref = this.watchMap.getNestedFolders(folderName); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { folder = _ref[_i]; _ref1 = this.watchMap.getFilenames(folder); for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { filename = _ref1[_j]; this.fileDeleted(filename); } if (this.watchers[folder]) { this.watchers[folder].close(); delete this.watchers[folder]; } _results.push(this.watchMap.unwatchFolder(folder)); } return _results; }; GlobWatcher.prototype.fileAdded = function(filename, folderName) { if (!this.isMatch(filename)) { return; } this.debug("file added: " + filename); this.watchMap.watchFile(filename, folderName); this.watchFile(filename); this.emit('added', filename); return Q(null); }; GlobWatcher.prototype.folderAdded = function(folderName) { if (!this.folderIsInteresting(folderName)) { return null; } this.debug("folder added: " + folderName); this.watchMap.watchFolder(folderName); this.watchFolder(folderName); return this.folderChanged(folderName); }; GlobWatcher.prototype.folderIsInteresting = function(folderName) { var folderSegments, set, _i, _len, _ref; folderSegments = folderName.split("/").slice(0, -1); _ref = this.minimatchSets; for (_i = 0, _len = _ref.length; _i < _len; _i++) { set = _ref[_i]; if (folderMatchesMinimatchPrefix(folderSegments, set)) { return true; } } return false; }; return GlobWatcher; })(events.EventEmitter); }).call(this);