Comparing version 0.3.3 to 0.4.0
144
index.js
var fs = require('fs'); | ||
var path = require('path'); | ||
var watch = require('watch'); | ||
var walker = require('walker'); | ||
@@ -13,3 +14,3 @@ var minimatch = require('minimatch'); | ||
* @param {Array<string>} files | ||
* @param {Array<string>} globs | ||
* @param {Array<string>} glob | ||
* @return {Watcher} | ||
@@ -52,9 +53,20 @@ * @public | ||
this.stopWatching = this.stopWatching.bind(this); | ||
this.watchdir(dir); | ||
recReaddir( | ||
dir, | ||
this.watchdir, | ||
this.register, | ||
this.emit.bind(this, 'ready') | ||
); | ||
this.filter = this.filter.bind(this); | ||
if (opts.poll) { | ||
this.polling = true; | ||
watch.createMonitor( | ||
dir, | ||
{ interval: opts.interval || DEFAULT_DELAY , filter: this.filter }, | ||
this.initPoller.bind(this) | ||
); | ||
} else { | ||
this.watchdir(dir); | ||
recReaddir( | ||
dir, | ||
this.watchdir, | ||
this.register, | ||
this.emit.bind(this, 'ready') | ||
); | ||
} | ||
} | ||
@@ -65,2 +77,26 @@ | ||
/** | ||
* Checks a file relative path against the globs array. | ||
* | ||
* @param {string} relativePath | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
Watcher.prototype.isFileIncluded = function(relativePath) { | ||
var globs = this.globs; | ||
var matched; | ||
if (globs.length) { | ||
for (var i = 0; i < globs.length; i++) { | ||
if (minimatch(relativePath, globs[i])) { | ||
matched = true; | ||
break; | ||
} | ||
} | ||
} else { | ||
matched = true; | ||
} | ||
return matched; | ||
}; | ||
/** | ||
* Register files that matches our globs to know what to type of event to | ||
@@ -84,15 +120,5 @@ * emit in the future. | ||
var relativePath = path.relative(this.root, filepath); | ||
var globs = this.globs; | ||
var matched; | ||
if (globs.length) { | ||
for (var i = 0; i < globs.length; i++) { | ||
if (minimatch(relativePath, globs[i])) { | ||
matched = true; | ||
break; | ||
} | ||
} | ||
} else { | ||
matched = true; | ||
if (!this.isFileIncluded(relativePath)) { | ||
return false; | ||
} | ||
if (!matched) return false; | ||
@@ -170,12 +196,15 @@ var dir = path.dirname(filepath); | ||
/** | ||
* Stop watching a dir.s | ||
* In polling mode stop watching files and directories, in normal mode, stop | ||
* watching files. | ||
* | ||
* @param {string} dir | ||
* @param {string} filepath | ||
* @private | ||
*/ | ||
Watcher.prototype.stopWatching = function(dir) { | ||
if (this.watched[dir]) { | ||
this.watched[dir].close(); | ||
this.watched[dir] = null; | ||
Watcher.prototype.stopWatching = function(filepath) { | ||
if (this.polling) { | ||
fs.unwatchFile(filepath); | ||
} else if (this.watched[filepath]) { | ||
this.watched[filepath].close(); | ||
this.watched[filepath] = null; | ||
} | ||
@@ -216,3 +245,3 @@ }; | ||
this.watchdir(fullPath); | ||
this.emitEvent('add', relativePath); | ||
this.emitEvent(ADD_EVENT, relativePath); | ||
} else { | ||
@@ -225,9 +254,9 @@ var registered = this.registered(fullPath); | ||
if (registered) { | ||
this.emitEvent('delete', relativePath); | ||
this.emitEvent(DELETE_EVENT, relativePath); | ||
} | ||
} else if (registered) { | ||
this.emitEvent('change', relativePath); | ||
this.emitEvent(CHANGE_EVENT, relativePath); | ||
} else { | ||
if (this.register(fullPath)) { | ||
this.emitEvent('add', relativePath); | ||
this.emitEvent(ADD_EVENT, relativePath); | ||
} | ||
@@ -252,6 +281,58 @@ } | ||
this.emit(type, file); | ||
}.bind(this), 100); | ||
}.bind(this), DEFAULT_DELAY); | ||
}; | ||
/** | ||
* Initiate the polling file watcher with the event emitter passed from | ||
* `watch.watchTree`. | ||
* | ||
* @param {EventEmitter} monitor | ||
* @public | ||
*/ | ||
Watcher.prototype.initPoller = function(monitor) { | ||
this.watched = monitor.files; | ||
monitor.on('changed', this.pollerEmit.bind(this, CHANGE_EVENT)); | ||
monitor.on('removed', this.pollerEmit.bind(this, DELETE_EVENT)); | ||
monitor.on('created', this.pollerEmit.bind(this, ADD_EVENT)); | ||
// 1 second wait because mtime is second-based. | ||
setTimeout(this.emit.bind(this, 'ready'), 1000); | ||
}; | ||
/** | ||
* Transform and emit an event comming from the poller. | ||
* | ||
* @param {EventEmitter} monitor | ||
* @public | ||
*/ | ||
Watcher.prototype.pollerEmit = function(type, file) { | ||
file = path.relative(this.root, file); | ||
this.emit(type, file); | ||
}; | ||
/** | ||
* Given a fullpath of a file or directory check if we need to watch it. | ||
* | ||
* @param {string} filepath | ||
* @param {object} stat | ||
* @public | ||
*/ | ||
Watcher.prototype.filter = function(filepath, stat) { | ||
return stat.isDirectory() || this.isFileIncluded( | ||
path.relative(this.root, filepath) | ||
); | ||
}; | ||
/** | ||
* Constants | ||
*/ | ||
var DEFAULT_DELAY = 100; | ||
var CHANGE_EVENT = 'change'; | ||
var DELETE_EVENT = 'delete'; | ||
var ADD_EVENT = 'add'; | ||
/** | ||
* Traverse a directory recursively calling `callback` on every directory. | ||
@@ -271,1 +352,2 @@ * | ||
} | ||
{ | ||
"name": "sane", | ||
"version": "0.3.3", | ||
"description": "Sane aims to be fast, small, and reliable file system watcher. No bells and whistles, just change events.", | ||
"version": "0.4.0", | ||
"description": "Sane aims to be fast, small, and reliable file system watcher.", | ||
"main": "index.js", | ||
@@ -21,3 +21,4 @@ "scripts": { | ||
"walker": "~1.0.5", | ||
"minimatch": "~0.2.14" | ||
"minimatch": "~0.2.14", | ||
"watch": "~0.10.0" | ||
}, | ||
@@ -24,0 +25,0 @@ "devDependencies": { |
@@ -17,6 +17,6 @@ sane | ||
### sane(dir, globs) | ||
### sane(dir, globs, options) | ||
Watches a directory and all it's descendant directorys for changes, deletions, and additions on files and directories. | ||
Shortcut for `new sane.Watcher(files, {globs: globs})`. | ||
Shortcut for `new sane.Watcher(files, {globs: globs, options})`. | ||
@@ -33,2 +33,4 @@ ```js | ||
For `options` see `sane.Watcher`. | ||
### sane.Watcher(dir, options) | ||
@@ -40,2 +42,4 @@ | ||
* `glob`: a single string glob pattern or an array of them. | ||
* `poll`: puts the watcher in polling mode. Under the hood that means `fs.watchFile`. | ||
* `interval`: indicates how often the files should be polled. (passed to `fs.watchFile`) | ||
@@ -42,0 +46,0 @@ For the glob pattern documentation, see [minimatch](https://github.com/isaacs/minimatch). |
298
test/test.js
@@ -12,137 +12,119 @@ var os = require('os'); | ||
before(function() { | ||
rimraf.sync(testdir); | ||
try { | ||
fs.mkdirSync(testdir); | ||
} catch (e) {} | ||
for (var i = 0; i < 10; i++) { | ||
fs.writeFileSync(jo(testdir, 'file_' + i), 'test_' + i); | ||
var subdir = jo(testdir, 'sub_' + i); | ||
try { | ||
fs.mkdirSync(subdir); | ||
describe('sane in polling mode', harness.bind(this, true)); | ||
describe('sand in normal mode', harness.bind(this, false)); | ||
function harness(isPolling) { | ||
before(function() { | ||
rimraf.sync(testdir); | ||
try { | ||
fs.mkdirSync(testdir); | ||
} catch (e) {} | ||
for (var j = 0; j < 10; j++) { | ||
fs.writeFileSync(jo(subdir, 'file_' + j), 'test_' + j); | ||
for (var i = 0; i < 10; i++) { | ||
fs.writeFileSync(jo(testdir, 'file_' + i), 'test_' + i); | ||
var subdir = jo(testdir, 'sub_' + i); | ||
try { | ||
fs.mkdirSync(subdir); | ||
} catch (e) {} | ||
for (var j = 0; j < 10; j++) { | ||
fs.writeFileSync(jo(subdir, 'file_' + j), 'test_' + j); | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
describe('sane(file)', function() { | ||
beforeEach(function () { | ||
this.watcher = sane(testdir); | ||
}); | ||
describe('sane(file)', function() { | ||
beforeEach(function () { | ||
this.watcher = new sane.Watcher(testdir, { poll: isPolling }); | ||
}); | ||
afterEach(function() { | ||
this.watcher.close(); | ||
}); | ||
it('emits a ready event', function(done) { | ||
this.watcher.on('ready', done); | ||
}); | ||
it('change emits event', function(done) { | ||
var testfile = jo(testdir, 'file_1'); | ||
this.watcher.on('change', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
afterEach(function() { | ||
this.watcher.close(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
it('emits change events for subdir files', function(done) { | ||
var testfile = jo(testdir, 'sub_1', 'file_1'); | ||
this.watcher.on('change', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
it('emits a ready event', function(done) { | ||
this.watcher.on('ready', done); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
it('adding a file will trigger a change', function(done) { | ||
var testfile = jo(testdir, 'file_x' + Math.floor(Math.random() * 10000)); | ||
this.watcher.on('add', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
it('change emits event', function(done) { | ||
var testfile = jo(testdir, 'file_1'); | ||
this.watcher.on('change', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
it('removing a file will emit delete event', function(done) { | ||
var testfile = jo(testdir, 'file_9'); | ||
this.watcher.on('delete', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
it('emits change events for subdir files', function(done) { | ||
var testfile = jo(testdir, 'sub_1', 'file_1'); | ||
this.watcher.on('change', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.unlink(testfile, 'wow'); | ||
}); | ||
}); | ||
it('removing a dir will emit delete event', function(done) { | ||
var subdir = jo(testdir, 'sub_9'); | ||
this.watcher.on('delete', function(filepath) { | ||
// Ignore delete events for files in the dir. | ||
if (path.dirname(filepath) === path.relative(testdir, subdir)) { | ||
return; | ||
} | ||
assert.equal(filepath, path.relative(testdir, subdir)); | ||
done(); | ||
it('adding a file will trigger a change', function(done) { | ||
var testfile = jo(testdir, 'file_x' + Math.floor(Math.random() * 10000)); | ||
this.watcher.on('add', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
this.watcher.on('ready', function() { | ||
rimraf.sync(subdir); | ||
}); | ||
}); | ||
it('adding a dir will emit an add event', function(done) { | ||
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000)); | ||
this.watcher.on('add', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, subdir)); | ||
done(); | ||
it('removing a file will emit delete event', function(done) { | ||
var testfile = jo(testdir, 'file_9'); | ||
this.watcher.on('delete', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.unlink(testfile, 'wow'); | ||
}); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.mkdirSync(subdir); | ||
}); | ||
}); | ||
it('adding in a new subdir will trigger an add event', function(done) { | ||
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000)); | ||
var testfile = jo(subdir, 'file_x' + Math.floor(Math.random() * 10000)); | ||
var i = 0; | ||
this.watcher.on('add', function(filepath) { | ||
if (++i === 1) { | ||
it('removing a dir will emit delete event', function(done) { | ||
var subdir = jo(testdir, 'sub_9'); | ||
this.watcher.on('delete', function(filepath) { | ||
// Ignore delete events for files in the dir. | ||
if (path.dirname(filepath) === path.relative(testdir, subdir)) { | ||
return; | ||
} | ||
assert.equal(filepath, path.relative(testdir, subdir)); | ||
} else { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
} | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.mkdirSync(subdir); | ||
defer(function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
this.watcher.on('ready', function() { | ||
rimraf.sync(subdir); | ||
}); | ||
}); | ||
}); | ||
it('closes watchers when dirs are deleted', function(done) { | ||
var subdir = jo(testdir, 'sub_1'); | ||
var testfile = jo(subdir, 'file_1'); | ||
var i = 0; | ||
this.watcher.on('add', function(filepath) { | ||
if (++i === 1) { | ||
it('adding a dir will emit an add event', function(done) { | ||
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000)); | ||
this.watcher.on('add', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, subdir)); | ||
} else { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
} | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.mkdirSync(subdir); | ||
}); | ||
}); | ||
this.watcher.on('ready', function() { | ||
rimraf.sync(subdir); | ||
defer(function() { | ||
it('adding in a new subdir will trigger an add event', function(done) { | ||
var subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000)); | ||
var testfile = jo(subdir, 'file_x' + Math.floor(Math.random() * 10000)); | ||
var i = 0; | ||
this.watcher.on('add', function(filepath) { | ||
if (++i === 1) { | ||
assert.equal(filepath, path.relative(testdir, subdir)); | ||
} else { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
} | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.mkdirSync(subdir); | ||
@@ -154,46 +136,72 @@ defer(function() { | ||
}); | ||
}); | ||
it('should be ok to remove and then add the same file', function(done) { | ||
var testfile = jo(testdir, 'sub_8', 'file_1'); | ||
var i = 0; | ||
this.watcher.on('add', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
it('closes watchers when dirs are deleted', function(done) { | ||
var subdir = jo(testdir, 'sub_1'); | ||
var testfile = jo(subdir, 'file_1'); | ||
var i = 0; | ||
this.watcher.on('add', function(filepath) { | ||
if (++i === 1) { | ||
assert.equal(filepath, path.relative(testdir, subdir)); | ||
} else { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
} | ||
}); | ||
this.watcher.on('ready', function() { | ||
rimraf.sync(subdir); | ||
defer(function() { | ||
fs.mkdirSync(subdir); | ||
defer(function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
this.watcher.on('delete', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.unlink(testfile); | ||
defer(function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
it('should be ok to remove and then add the same file', function(done) { | ||
var testfile = jo(testdir, 'sub_8', 'file_1'); | ||
var i = 0; | ||
this.watcher.on('add', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
}); | ||
this.watcher.on('delete', function(filepath) { | ||
assert.equal(filepath, path.relative(testdir, testfile)); | ||
done(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.unlink(testfile); | ||
defer(function() { | ||
fs.writeFileSync(testfile, 'wow'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('sane(file, glob)', function() { | ||
beforeEach(function () { | ||
this.watcher = sane(testdir, ['**/file_1', '**/file_2']); | ||
}); | ||
describe('sane(file, glob)', function() { | ||
beforeEach(function () { | ||
this.watcher = new sane.Watcher( | ||
testdir, | ||
{ glob: ['**/file_1', '**/file_2'], poll: isPolling } | ||
); | ||
}); | ||
afterEach(function() { | ||
this.watcher.close(); | ||
}); | ||
afterEach(function() { | ||
this.watcher.close(); | ||
}); | ||
it('ignore files according to glob', function (done) { | ||
var i = 0; | ||
this.watcher.on('change', function(filepath) { | ||
assert.ok(filepath.match(/file_(1|2)/), 'only file_1 and file_2'); | ||
if (++i == 2) done(); | ||
it('ignore files according to glob', function (done) { | ||
var i = 0; | ||
this.watcher.on('change', function(filepath) { | ||
assert.ok(filepath.match(/file_(1|2)/), 'only file_1 and file_2'); | ||
if (++i == 2) done(); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(jo(testdir, 'file_1'), 'wow'); | ||
fs.writeFileSync(jo(testdir, 'file_9'), 'wow'); | ||
fs.writeFileSync(jo(testdir, 'file_3'), 'wow'); | ||
fs.writeFileSync(jo(testdir, 'file_2'), 'wow'); | ||
}); | ||
}); | ||
this.watcher.on('ready', function() { | ||
fs.writeFileSync(jo(testdir, 'file_1'), 'wow'); | ||
fs.writeFileSync(jo(testdir, 'file_9'), 'wow'); | ||
fs.writeFileSync(jo(testdir, 'file_3'), 'wow'); | ||
fs.writeFileSync(jo(testdir, 'file_2'), 'wow'); | ||
}); | ||
}); | ||
}); | ||
} | ||
@@ -200,0 +208,0 @@ function defer(fn) { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
16199
489
62
3
+ Addedwatch@~0.10.0
+ Addedwatch@0.10.0(transitive)