Comparing version 0.5.1 to 0.6.0
450
lib/gaze.js
@@ -5,3 +5,3 @@ /* | ||
* | ||
* Copyright (c) 2013 Kyle Robinson Young | ||
* Copyright (c) 2014 Kyle Robinson Young | ||
* Licensed under the MIT license. | ||
@@ -18,13 +18,9 @@ */ | ||
var globule = require('globule'); | ||
var nextback = require('nextback'); | ||
var helper = require('./helper'); | ||
var platform = require('./platform'); | ||
// shim setImmediate for node v0.8 | ||
var setImmediate = require('timers').setImmediate; | ||
if (typeof setImmediate !== 'function') { | ||
setImmediate = process.nextTick; | ||
} | ||
// keep track of instances to call multiple times for backwards compatibility | ||
var instances = []; | ||
// globals | ||
var delay = 10; | ||
// `Gaze` EventEmitter object to return in the callback | ||
@@ -35,3 +31,8 @@ function Gaze(patterns, opts, done) { | ||
// If second arg is the callback | ||
// Optional arguments | ||
if (typeof patterns === 'function') { | ||
done = patterns; | ||
patterns = null; | ||
opts = {}; | ||
} | ||
if (typeof opts === 'function') { | ||
@@ -45,3 +46,3 @@ done = opts; | ||
opts.mark = true; | ||
opts.interval = opts.interval || 100; | ||
opts.interval = opts.interval || 500; | ||
opts.debounceDelay = opts.debounceDelay || 500; | ||
@@ -51,2 +52,15 @@ opts.cwd = opts.cwd || process.cwd(); | ||
// Default error handler to prevent emit('error') throwing magically for us | ||
this.on('error', function(error) { | ||
if (self.listeners('error').length > 1) { | ||
return self.removeListener('error', this); | ||
} | ||
nextback(function() { | ||
done.call(self, error, self); | ||
})(); | ||
}); | ||
// File watching mode to use when adding files to the platform | ||
this._mode = opts.mode || 'auto'; | ||
// Default done callback | ||
@@ -83,4 +97,13 @@ done = done || function() {}; | ||
// keep the process alive | ||
this._keepalive = setInterval(function() {}, 200); | ||
this._keepalive = setInterval(platform.tick.bind(platform), opts.interval); | ||
// Keep track of all instances created | ||
this._instanceNum = instances.length; | ||
instances.push(this); | ||
// Keep track of safewriting and debounce timeouts | ||
this._safewriting = null; | ||
this._safewriteTimeout = null; | ||
this._timeoutId = null; | ||
return this; | ||
@@ -104,3 +127,2 @@ } | ||
var filepath = args[1]; | ||
var timeoutId; | ||
@@ -125,2 +147,22 @@ // If not added/deleted/changed/renamed then just emit the event | ||
// Detect safewrite events, if file is deleted and then added/renamed, assume a safewrite happened | ||
if (e === 'deleted' && this._safewriting == null) { | ||
this._safewriting = filepath; | ||
this._safewriteTimeout = setTimeout(function() { | ||
// Just a normal delete, carry on | ||
Gaze.super_.prototype.emit.apply(self, args); | ||
Gaze.super_.prototype.emit.apply(self, ['all', e].concat([].slice.call(args, 1))); | ||
self._safewriting = null; | ||
}, this.options.debounceDelay); | ||
return this; | ||
} else if ((e === 'added' || e === 'renamed') && this._safewriting === filepath) { | ||
clearTimeout(this._safewriteTimeout); | ||
this._safewriteTimeout = setTimeout(function() { | ||
self._safewriting = null; | ||
}, this.options.debounceDelay); | ||
args[0] = e = 'changed'; | ||
} else if (e === 'deleted' && this._safewriting === filepath) { | ||
return this; | ||
} | ||
// If cached doesnt exist, create a delay before running the next | ||
@@ -131,4 +173,4 @@ // then emit the event | ||
helper.objectPush(self._cached, filepath, e); | ||
clearTimeout(timeoutId); | ||
timeoutId = setTimeout(function() { | ||
clearTimeout(this._timeoutId); | ||
this._timeoutId = setTimeout(function() { | ||
delete self._cached[filepath]; | ||
@@ -159,20 +201,5 @@ }, this.options.debounceDelay); | ||
Gaze.prototype.close = function(_reset) { | ||
var self = this; | ||
_reset = _reset === false ? false : true; | ||
Object.keys(self._watchers).forEach(function(file) { | ||
self._watchers[file].close(); | ||
}); | ||
self._watchers = Object.create(null); | ||
Object.keys(this._watched).forEach(function(dir) { | ||
self._unpollDir(dir); | ||
}); | ||
if (_reset) { | ||
self._watched = Object.create(null); | ||
setTimeout(function() { | ||
self.emit('end'); | ||
self.removeAllListeners(); | ||
clearInterval(self._keepalive); | ||
}, delay + 100); | ||
} | ||
return self; | ||
instances.splice(this._instanceNum, 1); | ||
platform.closeAll(); | ||
this.emit('end'); | ||
}; | ||
@@ -182,269 +209,154 @@ | ||
Gaze.prototype.add = function(files, done) { | ||
var self = this; | ||
if (typeof files === 'string') { files = [files]; } | ||
this._patterns = helper.unique.apply(null, [this._patterns, files]); | ||
files = globule.find(this._patterns, this.options); | ||
this._addToWatched(files); | ||
this.close(false); | ||
this._initWatched(done); | ||
// If no matching files | ||
if (files.length < 1) { | ||
// Defer to emitting to give a chance to attach event handlers. | ||
nextback(function() { | ||
self.emit('ready', self); | ||
if (done) done.call(self, null, self); | ||
self.emit('nomatch'); | ||
})(); | ||
return; | ||
} | ||
// Set the platform mode before adding files | ||
platform.mode = self._mode; | ||
helper.forEachSeries(files, function(file, next) { | ||
try { | ||
platform(path.join(self.options.cwd, file), self._trigger.bind(self)); | ||
} catch (err) { | ||
self.emit('error', err); | ||
} | ||
next(); | ||
}, function() { | ||
// A little delay here for backwards compatibility, lol | ||
setTimeout(function() { | ||
self.emit('ready', self); | ||
if (done) done.call(self, null, self); | ||
}, 10); | ||
}); | ||
}; | ||
// Dont increment patterns and dont call done if nothing added | ||
Gaze.prototype._internalAdd = function(file, done) { | ||
var files = []; | ||
if (helper.isDir(file)) { | ||
files = [helper.markDir(file)].concat(globule.find(this._patterns, this.options)); | ||
} else { | ||
if (globule.isMatch(this._patterns, file, this.options)) { | ||
files = [file]; | ||
} | ||
// Call when the platform has triggered | ||
Gaze.prototype._trigger = function(error, event, filepath, newFile) { | ||
if (error) { return this.emit('error', error); } | ||
// Set the platform mode before adding files | ||
platform.mode = this._mode; | ||
if (event === 'change' && helper.isDir(filepath)) { | ||
this._wasAdded(filepath); | ||
} else if (event === 'change') { | ||
this._emitAll('changed', filepath); | ||
} else if (event === 'delete') { | ||
// Close out deleted filepaths (important to make safewrite detection work) | ||
platform.close(filepath); | ||
this._emitAll('deleted', filepath); | ||
} else if (event === 'rename') { | ||
// TODO: This occasionally throws, figure out why or use the old style rename detect | ||
// The handle(26) returned by watching [filename] is the same with an already watched path([filename]) | ||
this._emitAll('renamed', newFile, filepath); | ||
} | ||
if (files.length > 0) { | ||
this._addToWatched(files); | ||
this.close(false); | ||
this._initWatched(done); | ||
} | ||
}; | ||
// Remove file/dir from `watched` | ||
Gaze.prototype.remove = function(file) { | ||
// If a folder received a change event, investigate | ||
Gaze.prototype._wasAdded = function(dir) { | ||
var self = this; | ||
if (this._watched[file]) { | ||
// is dir, remove all files | ||
this._unpollDir(file); | ||
delete this._watched[file]; | ||
} else { | ||
// is a file, find and remove | ||
Object.keys(this._watched).forEach(function(dir) { | ||
var index = self._watched[dir].indexOf(file); | ||
if (index !== -1) { | ||
self._unpollFile(file); | ||
self._watched[dir].splice(index, 1); | ||
return false; | ||
var dirstat = fs.statSync(dir); | ||
fs.readdir(dir, function(err, current) { | ||
helper.forEachSeries(current, function(file, next) { | ||
var filepath = path.join(dir, file); | ||
if (!fs.existsSync(filepath)) return next(); | ||
var stat = fs.lstatSync(filepath); | ||
if ((dirstat.mtime - stat.mtime) <= 0) { | ||
var relpath = path.relative(self.options.cwd, filepath); | ||
if (stat.isDirectory()) { | ||
// If it was a dir, watch the dir and emit that it was added | ||
platform(filepath, self._trigger.bind(self)); | ||
self._emitAll('added', filepath); | ||
self._wasAddedSub(filepath); | ||
} else if (globule.isMatch(self._patterns, relpath, self.options)) { | ||
// Otherwise if the file matches, emit added | ||
platform(filepath, self._trigger.bind(self)); | ||
self._emitAll('added', filepath); | ||
} | ||
} | ||
next(); | ||
}); | ||
} | ||
if (this._watchers[file]) { | ||
this._watchers[file].close(); | ||
} | ||
return this; | ||
}); | ||
}; | ||
// Return watched files | ||
Gaze.prototype.watched = function() { | ||
return this._watched; | ||
}; | ||
// Returns `watched` files with relative paths to process.cwd() | ||
Gaze.prototype.relative = function(dir, unixify) { | ||
// If a sub folder was added, investigate further | ||
// Such as with grunt.file.write('new_dir/tmp.js', '') as it will create the folder and file simultaneously | ||
Gaze.prototype._wasAddedSub = function(dir) { | ||
var self = this; | ||
var relative = Object.create(null); | ||
var relDir, relFile, unixRelDir; | ||
var cwd = this.options.cwd || process.cwd(); | ||
if (dir === '') { dir = '.'; } | ||
dir = helper.markDir(dir); | ||
unixify = unixify || false; | ||
Object.keys(this._watched).forEach(function(dir) { | ||
relDir = path.relative(cwd, dir) + path.sep; | ||
if (relDir === path.sep) { relDir = '.'; } | ||
unixRelDir = unixify ? helper.unixifyPathSep(relDir) : relDir; | ||
relative[unixRelDir] = self._watched[dir].map(function(file) { | ||
relFile = path.relative(path.join(cwd, relDir) || '', file || ''); | ||
if (helper.isDir(file)) { | ||
relFile = helper.markDir(relFile); | ||
fs.readdir(dir, function(err, current) { | ||
helper.forEachSeries(current, function(file, next) { | ||
var filepath = path.join(dir, file); | ||
var relpath = path.relative(self.options.cwd, filepath); | ||
try { | ||
if (fs.lstatSync(filepath).isDirectory()) { | ||
self._wasAdded(filepath); | ||
} else if (globule.isMatch(self._patterns, relpath, self.options)) { | ||
// Make sure to watch the newly added sub file | ||
platform(filepath, self._trigger.bind(self)); | ||
self._emitAll('added', filepath); | ||
} | ||
} catch (err) { | ||
self.emit('error', err); | ||
} | ||
if (unixify) { | ||
relFile = helper.unixifyPathSep(relFile); | ||
} | ||
return relFile; | ||
next(); | ||
}); | ||
}); | ||
if (dir && unixify) { | ||
dir = helper.unixifyPathSep(dir); | ||
} | ||
return dir ? relative[dir] || [] : relative; | ||
}; | ||
// Adds files and dirs to watched | ||
Gaze.prototype._addToWatched = function(files) { | ||
for (var i = 0; i < files.length; i++) { | ||
var file = files[i]; | ||
var filepath = path.resolve(this.options.cwd, file); | ||
var dirname = (helper.isDir(file)) ? filepath : path.dirname(filepath); | ||
dirname = helper.markDir(dirname); | ||
// If a new dir is added | ||
if (helper.isDir(file) && !(filepath in this._watched)) { | ||
helper.objectPush(this._watched, filepath, []); | ||
} | ||
if (file.slice(-1) === '/') { filepath += path.sep; } | ||
helper.objectPush(this._watched, path.dirname(filepath) + path.sep, filepath); | ||
// add folders into the mix | ||
var readdir = fs.readdirSync(dirname); | ||
for (var j = 0; j < readdir.length; j++) { | ||
var dirfile = path.join(dirname, readdir[j]); | ||
if (fs.statSync(dirfile).isDirectory()) { | ||
helper.objectPush(this._watched, dirname, dirfile + path.sep); | ||
} | ||
} | ||
// Wrapper for emit to ensure we emit on all instances | ||
Gaze.prototype._emitAll = function() { | ||
var args = Array.prototype.slice.call(arguments); | ||
for (var i = 0; i < instances.length; i++) { | ||
instances[i].emit.apply(instances[i], args); | ||
} | ||
return this; | ||
}; | ||
Gaze.prototype._watchDir = function(dir, done) { | ||
var self = this; | ||
var timeoutId; | ||
try { | ||
this._watchers[dir] = fs.watch(dir, function(event) { | ||
// race condition. Let's give the fs a little time to settle down. so we | ||
// don't fire events on non existent files. | ||
clearTimeout(timeoutId); | ||
timeoutId = setTimeout(function() { | ||
// race condition. Ensure that this directory is still being watched | ||
// before continuing. | ||
if ((dir in self._watchers) && fs.existsSync(dir)) { | ||
done(null, dir); | ||
} | ||
}, delay + 100); | ||
}); | ||
} catch (err) { | ||
return this._handleError(err); | ||
} | ||
// Remove file/dir from `watched` | ||
Gaze.prototype.remove = function(file) { | ||
platform.close(file); | ||
return this; | ||
}; | ||
Gaze.prototype._unpollFile = function(file) { | ||
if (this._pollers[file]) { | ||
fs.unwatchFile(file, this._pollers[file] ); | ||
delete this._pollers[file]; | ||
} | ||
// Return watched files | ||
Gaze.prototype.watched = function(done) { | ||
done = nextback(done || function() {}); | ||
helper.flatToTree(platform.getWatchedPaths(), this.options.cwd, false, done); | ||
return this; | ||
}; | ||
Gaze.prototype._unpollDir = function(dir) { | ||
this._unpollFile(dir); | ||
for (var i = 0; i < this._watched[dir].length; i++) { | ||
this._unpollFile(this._watched[dir][i]); | ||
// Returns `watched` files with relative paths to cwd | ||
Gaze.prototype.relative = function(dir, unixify, done) { | ||
if (typeof dir === 'function') { | ||
done = dir; | ||
dir = null; | ||
unixify = false; | ||
} | ||
}; | ||
Gaze.prototype._pollFile = function(file, done) { | ||
var opts = { persistent: true, interval: this.options.interval }; | ||
if (!this._pollers[file]) { | ||
this._pollers[file] = function(curr, prev) { | ||
done(null, file); | ||
}; | ||
try { | ||
fs.watchFile(file, opts, this._pollers[file]); | ||
} catch (err) { | ||
return this._handleError(err); | ||
if (typeof unixify === 'function') { | ||
done = unixify; | ||
unixify = false; | ||
} | ||
done = nextback(done || function() {}); | ||
helper.flatToTree(platform.getWatchedPaths(), this.options.cwd, true, unixify, function(err, relative) { | ||
if (dir) { | ||
if (unixify) { | ||
dir = helper.unixifyPathSep(dir); | ||
} | ||
// Better guess what to return for backwards compatibility | ||
return done(null, relative[dir] || relative[dir + (unixify ? '/' : path.sep)] || []); | ||
} | ||
} | ||
return done(null, relative); | ||
}); | ||
return this; | ||
}; | ||
// Initialize the actual watch on `watched` files | ||
Gaze.prototype._initWatched = function(done) { | ||
var self = this; | ||
var cwd = this.options.cwd || process.cwd(); | ||
var curWatched = Object.keys(self._watched); | ||
// if no matching files | ||
if (curWatched.length < 1) { | ||
// Defer to emitting to give a chance to attach event handlers. | ||
setImmediate(function () { | ||
self.emit('ready', self); | ||
if (done) { done.call(self, null, self); } | ||
self.emit('nomatch'); | ||
}); | ||
return; | ||
} | ||
helper.forEachSeries(curWatched, function(dir, next) { | ||
dir = dir || ''; | ||
var files = self._watched[dir]; | ||
// Triggered when a watched dir has an event | ||
self._watchDir(dir, function(event, dirpath) { | ||
var relDir = cwd === dir ? '.' : path.relative(cwd, dir); | ||
relDir = relDir || ''; | ||
fs.readdir(dirpath, function(err, current) { | ||
if (err) { return self.emit('error', err); } | ||
if (!current) { return; } | ||
try { | ||
// append path.sep to directories so they match previous. | ||
current = current.map(function(curPath) { | ||
if (fs.existsSync(path.join(dir, curPath)) && fs.statSync(path.join(dir, curPath)).isDirectory()) { | ||
return curPath + path.sep; | ||
} else { | ||
return curPath; | ||
} | ||
}); | ||
} catch (err) { | ||
// race condition-- sometimes the file no longer exists | ||
} | ||
// Get watched files for this dir | ||
var previous = self.relative(relDir); | ||
// If file was deleted | ||
previous.filter(function(file) { | ||
return current.indexOf(file) < 0; | ||
}).forEach(function(file) { | ||
if (!helper.isDir(file)) { | ||
var filepath = path.join(dir, file); | ||
self.remove(filepath); | ||
self.emit('deleted', filepath); | ||
} | ||
}); | ||
// If file was added | ||
current.filter(function(file) { | ||
return previous.indexOf(file) < 0; | ||
}).forEach(function(file) { | ||
// Is it a matching pattern? | ||
var relFile = path.join(relDir, file); | ||
// Add to watch then emit event | ||
self._internalAdd(relFile, function() { | ||
self.emit('added', path.join(dir, file)); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// Watch for change/rename events on files | ||
files.forEach(function(file) { | ||
if (helper.isDir(file)) { return; } | ||
self._pollFile(file, function(err, filepath) { | ||
// Only emit changed if the file still exists | ||
// Prevents changed/deleted duplicate events | ||
if (fs.existsSync(filepath)) { | ||
self.emit('changed', filepath); | ||
} | ||
}); | ||
}); | ||
next(); | ||
}, function() { | ||
// Return this instance of Gaze | ||
// delay before ready solves a lot of issues | ||
setTimeout(function() { | ||
self.emit('ready', self); | ||
if (done) { done.call(self, null, self); } | ||
}, delay + 100); | ||
}); | ||
}; | ||
// If an error, handle it here | ||
Gaze.prototype._handleError = function(err) { | ||
if (err.code === 'EMFILE') { | ||
return this.emit('error', new Error('EMFILE: Too many opened files.')); | ||
} | ||
return this.emit('error', err); | ||
}; |
@@ -0,1 +1,9 @@ | ||
/* | ||
* gaze | ||
* https://github.com/shama/gaze | ||
* | ||
* Copyright (c) 2014 Kyle Robinson Young | ||
* Licensed under the MIT license. | ||
*/ | ||
'use strict'; | ||
@@ -9,3 +17,3 @@ | ||
if (typeof dir !== 'string') { return false; } | ||
return (dir.slice(-(path.sep.length)) === path.sep); | ||
return (dir.slice(-(path.sep.length)) === path.sep) || (dir.slice(-1) === '/'); | ||
}; | ||
@@ -36,2 +44,45 @@ | ||
// Converts a flat list of paths to the old style tree | ||
helper.flatToTree = function flatToTree(files, cwd, relative, unixify, done) { | ||
cwd = helper.markDir(cwd); | ||
var tree = Object.create(null); | ||
helper.forEachSeries(files, function(filepath, next) { | ||
var parent = path.dirname(filepath) + path.sep; | ||
// If parent outside cwd, ignore | ||
if (path.relative(cwd, parent) === '..') { | ||
return next(); | ||
} | ||
// If we want relative paths | ||
if (relative === true) { | ||
if (path.resolve(parent) === path.resolve(cwd)) { | ||
parent = './'; | ||
} else { | ||
parent = path.relative(cwd, parent) + path.sep; | ||
} | ||
filepath = path.relative(path.join(cwd, parent), filepath) + (helper.isDir(filepath) ? path.sep : ''); | ||
} | ||
// If we want to transform paths to unix seps | ||
if (unixify === true) { | ||
filepath = helper.unixifyPathSep(filepath); | ||
if (parent !== './') { | ||
parent = helper.unixifyPathSep(parent); | ||
} | ||
} | ||
if (!parent) { return next(); } | ||
if (!Array.isArray(tree[parent])) { | ||
tree[parent] = []; | ||
} | ||
tree[parent].push(filepath); | ||
next(); | ||
}, function() { | ||
done(null, tree); | ||
}); | ||
}; | ||
/** | ||
@@ -51,12 +102,16 @@ * Lo-Dash 1.0.1 <http://lodash.com/> | ||
helper.forEachSeries = function forEachSeries(arr, iterator, callback) { | ||
if (!arr.length) { return callback(); } | ||
callback = callback || function () {}; | ||
if (!arr.length) { | ||
return callback(); | ||
} | ||
var completed = 0; | ||
var iterate = function() { | ||
var iterate = function () { | ||
iterator(arr[completed], function (err) { | ||
if (err) { | ||
callback(err); | ||
callback = function() {}; | ||
} else { | ||
callback = function () {}; | ||
} | ||
else { | ||
completed += 1; | ||
if (completed === arr.length) { | ||
if (completed >= arr.length) { | ||
callback(null); | ||
@@ -63,0 +118,0 @@ } else { |
{ | ||
"name": "gaze", | ||
"description": "A globbing fs.watch wrapper built from the best parts of other fine watch libs.", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"homepage": "https://github.com/shama/gaze", | ||
@@ -23,3 +23,3 @@ "author": { | ||
], | ||
"main": "lib/gaze", | ||
"main": "index.js", | ||
"engines": { | ||
@@ -29,24 +29,38 @@ "node": ">= 0.8.0" | ||
"scripts": { | ||
"test": "grunt nodeunit -v" | ||
"test": "grunt nodeunit -v", | ||
"install": "node-gyp rebuild" | ||
}, | ||
"dependencies": { | ||
"globule": "~0.1.0" | ||
"globule": "~0.2.0", | ||
"nextback": "~0.1.0", | ||
"bindings": "~1.2.0", | ||
"nan": "~0.8.0" | ||
}, | ||
"devDependencies": { | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-nodeunit": "~0.2.0", | ||
"grunt-contrib-jshint": "~0.6.0", | ||
"grunt-benchmark": "~0.2.0", | ||
"grunt-contrib-nodeunit": "~0.3.3", | ||
"grunt-contrib-jshint": "~0.9.2", | ||
"grunt-cli": "~0.1.13", | ||
"async": "~0.2.10", | ||
"rimraf": "~2.2.6" | ||
"rimraf": "~2.2.6", | ||
"ascii-table": "0.0.4" | ||
}, | ||
"keywords": [ | ||
"watch", | ||
"glob" | ||
"watcher", | ||
"watching", | ||
"fs.watch", | ||
"fswatcher", | ||
"fs", | ||
"glob", | ||
"utility" | ||
], | ||
"files": [ | ||
"index.js", | ||
"lib", | ||
"src", | ||
"binding.gyp", | ||
"AUTHORS", | ||
"LICENSE-MIT" | ||
] | ||
} |
@@ -1,2 +0,2 @@ | ||
# gaze [![Build Status](https://travis-ci.org/shama/gaze.png?branch=master)](https://travis-ci.org/shama/gaze) | ||
# gaze [![Build Status](http://img.shields.io/travis/shama/gaze.svg)](https://travis-ci.org/shama/gaze) [![gittip.com/shama](http://img.shields.io/gittip/shama.svg)](https://www.gittip.com/shama) | ||
@@ -8,2 +8,14 @@ A globbing fs.watch wrapper built from the best parts of other fine watch libs. | ||
## Features | ||
[![NPM](https://nodei.co/npm/gaze.png?downloads=true)](https://nodei.co/npm/gaze/) | ||
* Consistent events on OSX, Linux and Windows | ||
* Very fast start up and response time | ||
* High test coverage | ||
* Uses native OS events but falls back to stat polling | ||
* Option to force stat polling with special file systems such as networked | ||
* Downloaded over 400K times a month | ||
* Used by [Grunt](http://gruntjs.com), [gulp](http://gulpjs.com), [Tower](http://tower.github.io/) and many others | ||
## Usage | ||
@@ -22,3 +34,5 @@ Install the module with: `npm install gaze` or place into your `package.json` | ||
// Get all watched files | ||
console.log(this.watched()); | ||
this.watched(function(watched) { | ||
console.log(watched); | ||
}); | ||
@@ -46,3 +60,5 @@ // On file changed | ||
// Get watched files with relative paths | ||
console.log(this.relative()); | ||
this.relative(function(err, files) { | ||
console.log(files); | ||
}); | ||
}); | ||
@@ -74,9 +90,24 @@ | ||
```javascript | ||
gaze('**/*', function() { | ||
this.on('error', function(err) { | ||
// Handle error here | ||
}); | ||
gaze('**/*', function(error, watcher) { | ||
if (error) { | ||
// Handle error if it occured while starting up | ||
} | ||
}); | ||
// Or with the alternative interface | ||
var gaze = new Gaze(); | ||
gaze.on('error', function(error) { | ||
// Handle error here | ||
}); | ||
gaze.add('**/*'); | ||
``` | ||
#### `EMFILE` errors | ||
By default, gaze will use native OS events and then fallback to slower stat polling when an `EMFILE` error is reached. Gaze will still emit or return the error as the first argument of the ready callback for you to handle. | ||
It is recommended to advise your users to increase their file descriptor limits to utilize the faster native OS watching. Especially on OSX where the default descriptor limit is 256. | ||
In some cases, native OS events will not work. Such as with networked file systems or vagrant. It is recommended to set the option `mode: 'poll'` to always stat poll for those situations. | ||
### Minimatch / Glob | ||
@@ -89,3 +120,3 @@ | ||
### gaze(patterns, [options], callback) | ||
### gaze([patterns, options, callback]) | ||
@@ -100,3 +131,3 @@ * `patterns` {String|Array} File patterns to be matched | ||
Create a Gaze object by instanting the `gaze.Gaze` class. | ||
Create a Gaze object by instancing the `gaze.Gaze` class. | ||
@@ -114,2 +145,4 @@ ```javascript | ||
file/event | ||
* `mode` {string} Force the watch mode. Either `'auto'` (default), `'watch'` (force native events), or `'poll'` (force stat polling). | ||
* `cwd` {string} The current working directory to base file patterns from. Default is `process.cwd()`. | ||
@@ -136,6 +169,8 @@ #### Events | ||
recurse directories. | ||
* `watched()` Returns the currently watched files. | ||
* `relative([dir, unixify])` Returns the currently watched files with relative paths. | ||
* `watched([callback])` Returns the currently watched files. | ||
* `callback` {function} Calls with `function(err, files)`. | ||
* `relative([dir, unixify, callback])` Returns the currently watched files with relative paths. | ||
* `dir` {string} Only return relative files for this directory. | ||
* `unixify` {boolean} Return paths with `/` instead of `\\` if on Windows. | ||
* `callback` {function} Calls with `function(err, files)`. | ||
@@ -146,14 +181,12 @@ ## FAQs | ||
I liked parts of other `fs.watch` wrappers but none had all the features I | ||
needed. This lib combines the features I needed from other fine watch libs: | ||
Speedy data behavior from | ||
[paulmillr's chokidar](https://github.com/paulmillr/chokidar), API interface | ||
from [mikeal's watch](https://github.com/mikeal/watch) and file globbing using | ||
[isaacs's glob](https://github.com/isaacs/node-glob) which is also used by | ||
[cowboy's Grunt](https://github.com/gruntjs/grunt). | ||
needed when this library was originally written. This lib once combined the features I needed from other fine watch libs | ||
but now has taken on a life of it's own (**gaze doesn't wrap `fs.watch` or `fs.watchFile` anymore**). | ||
### How do I fix the error `EMFILE: Too many opened files.`? | ||
This is because of your system's max opened file limit. For OSX the default is | ||
very low (256). Increase your limit temporarily with `ulimit -n 10480`, the | ||
number being the new max limit. | ||
Other great watch libraries to try are: | ||
* [paulmillr's chokidar](https://github.com/paulmillr/chokidar) | ||
* [mikeal's watch](https://github.com/mikeal/watch) | ||
* [github's pathwatcher](https://github.com/atom/node-pathwatcher) | ||
* [bevry's watchr](https://github.com/bevry/watchr) | ||
## Contributing | ||
@@ -165,2 +198,3 @@ In lieu of a formal styleguide, take care to maintain the existing coding style. | ||
## Release History | ||
* 0.6.0 - Uses native OS events (fork of pathwatcher) but can fall back to stat polling. Everything is async to avoid blocking, including `relative()` and `watched()`. Better error handling. Update to globule@0.2.0. No longer watches `cwd` by default. Added `mode` option. Better `EMFILE` message. Avoids `ENOENT` errors with symlinks. All constructor arguments are optional. | ||
* 0.5.1 - Use setImmediate (process.nextTick for node v0.8) to defer ready/nomatch events (@amasad). | ||
@@ -189,3 +223,3 @@ * 0.5.0 - Process is now kept alive while watching files. Emits a nomatch event when no files are matching. | ||
## License | ||
Copyright (c) 2013 Kyle Robinson Young | ||
Copyright (c) 2014 Kyle Robinson Young | ||
Licensed under the MIT license. |
Sorry, the diff of this file is not supported yet
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
Native code
Supply chain riskContains native code which could be a vector to obscure malicious code, and generally decrease the likelihood of reproducible or reliable installs.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
88393
21
1251
215
4
5
2
+ Addedbindings@~1.2.0
+ Addednan@~0.8.0
+ Addednextback@~0.1.0
+ Addedbindings@1.2.1(transitive)
+ Addedglob@3.2.11(transitive)
+ Addedglobule@0.2.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedlodash@2.4.2(transitive)
+ Addedminimatch@0.3.0(transitive)
+ Addednan@0.8.0(transitive)
+ Addednextback@0.1.0(transitive)
- Removedglob@3.1.21(transitive)
- Removedglobule@0.1.0(transitive)
- Removedgraceful-fs@1.2.3(transitive)
- Removedinherits@1.0.2(transitive)
- Removedlodash@1.0.2(transitive)
Updatedglobule@~0.2.0