Comparing version 0.6.4 to 1.0.0
482
lib/gaze.js
@@ -5,3 +5,3 @@ /* | ||
* | ||
* Copyright (c) 2014 Kyle Robinson Young | ||
* Copyright (c) 2016 Kyle Robinson Young | ||
* Licensed under the MIT license. | ||
@@ -15,24 +15,22 @@ */ | ||
var EE = require('events').EventEmitter; | ||
var fs = require('graceful-fs'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var globule = require('globule'); | ||
var nextback = require('nextback'); | ||
var helper = require('./helper'); | ||
var platform = require('./platform'); | ||
var isAbsolute = require('absolute-path'); | ||
// keep track of instances to call multiple times for backwards compatibility | ||
var instances = []; | ||
// shim setImmediate for node v0.8 | ||
var setImmediate = require('timers').setImmediate; | ||
if (typeof setImmediate !== 'function') { | ||
setImmediate = process.nextTick; | ||
} | ||
// globals | ||
var delay = 10; | ||
// `Gaze` EventEmitter object to return in the callback | ||
function Gaze(patterns, opts, done) { | ||
function Gaze (patterns, opts, done) { | ||
var self = this; | ||
EE.call(self); | ||
// Optional arguments | ||
if (typeof patterns === 'function') { | ||
done = patterns; | ||
patterns = null; | ||
opts = {}; | ||
} | ||
// If second arg is the callback | ||
if (typeof opts === 'function') { | ||
@@ -46,3 +44,3 @@ done = opts; | ||
opts.mark = true; | ||
opts.interval = opts.interval || 500; | ||
opts.interval = opts.interval || 100; | ||
opts.debounceDelay = opts.debounceDelay || 500; | ||
@@ -52,17 +50,4 @@ 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 | ||
done = done || function() {}; | ||
done = done || function () {}; | ||
@@ -97,13 +82,4 @@ // Remember our watched dir:files | ||
// keep the process alive | ||
this._keepalive = setInterval(platform.tick.bind(platform), opts.interval); | ||
this._keepalive = setInterval(function () {}, 200); | ||
// 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; | ||
@@ -114,3 +90,3 @@ } | ||
// Main entry point. Start watching and call done when setup | ||
module.exports = function gaze(patterns, opts, done) { | ||
module.exports = function gaze (patterns, opts, done) { | ||
return new Gaze(patterns, opts, done); | ||
@@ -122,3 +98,3 @@ }; | ||
// and debounce on duplicate events per file | ||
Gaze.prototype.emit = function() { | ||
Gaze.prototype.emit = function () { | ||
var self = this; | ||
@@ -129,2 +105,3 @@ var args = arguments; | ||
var filepath = args[1]; | ||
var timeoutId; | ||
@@ -139,3 +116,3 @@ // If not added/deleted/changed/renamed then just emit the event | ||
if (e === 'added') { | ||
Object.keys(this._cached).forEach(function(oldFile) { | ||
Object.keys(this._cached).forEach(function (oldFile) { | ||
if (self._cached[oldFile].indexOf('deleted') !== -1) { | ||
@@ -150,22 +127,2 @@ args[0] = e = 'renamed'; | ||
// 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 | ||
@@ -176,4 +133,4 @@ // then emit the event | ||
helper.objectPush(self._cached, filepath, e); | ||
clearTimeout(this._timeoutId); | ||
this._timeoutId = setTimeout(function() { | ||
clearTimeout(timeoutId); | ||
timeoutId = setTimeout(function () { | ||
delete self._cached[filepath]; | ||
@@ -189,7 +146,7 @@ }, this.options.debounceDelay); | ||
if (helper.isDir(filepath)) { | ||
fs.readdirSync(filepath).map(function(file) { | ||
fs.readdirSync(filepath).map(function (file) { | ||
return path.join(filepath, file); | ||
}).filter(function(file) { | ||
}).filter(function (file) { | ||
return globule.isMatch(self._patterns, file, self.options); | ||
}).forEach(function(file) { | ||
}).forEach(function (file) { | ||
self.emit('added', file); | ||
@@ -204,171 +161,292 @@ }); | ||
// Close watchers | ||
Gaze.prototype.close = function(_reset) { | ||
instances.splice(this._instanceNum, 1); | ||
platform.closeAll(); | ||
this.emit('end'); | ||
Gaze.prototype.close = function (_reset) { | ||
var self = this; | ||
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 !== false) { | ||
self._watched = Object.create(null); | ||
setTimeout(function () { | ||
self.emit('end'); | ||
self.removeAllListeners(); | ||
clearInterval(self._keepalive); | ||
}, delay + 100); | ||
} | ||
return self; | ||
}; | ||
// Add file patterns to be watched | ||
Gaze.prototype.add = function(files, done) { | ||
var self = this; | ||
Gaze.prototype.add = function (files, done) { | ||
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 { | ||
var filepath = (!isAbsolute(file)) ? path.join(self.options.cwd, file) : file; | ||
platform(filepath, self._trigger.bind(self)); | ||
} catch (err) { | ||
self.emit('error', err); | ||
// 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]; | ||
} | ||
next(); | ||
}, function() { | ||
// A little delay here for backwards compatibility, lol | ||
setTimeout(function() { | ||
self.emit('ready', self); | ||
if (done) done.call(self, null, self); | ||
}, 10); | ||
}); | ||
}; | ||
// 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); | ||
} | ||
}; | ||
// If a folder received a change event, investigate | ||
Gaze.prototype._wasAdded = function(dir) { | ||
// Remove file/dir from `watched` | ||
Gaze.prototype.remove = function (file) { | ||
var self = this; | ||
var dirstat = fs.statSync(dir); | ||
fs.readdir(dir, function(err, current) { | ||
if (err) { | ||
self.emit('error', err); | ||
return; | ||
} | ||
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)); | ||
if (globule.isMatch(self._patterns, relpath, self.options)) { | ||
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)); | ||
if (globule.isMatch(self._patterns, relpath, self.options)) { | ||
self._emitAll('added', filepath); | ||
} | ||
} | ||
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; | ||
} | ||
next(); | ||
}); | ||
}); | ||
} | ||
if (this._watchers[file]) { | ||
this._watchers[file].close(); | ||
} | ||
return this; | ||
}; | ||
// 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) { | ||
// 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) { | ||
var self = this; | ||
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); | ||
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); | ||
} | ||
next(); | ||
if (unixify) { | ||
relFile = helper.unixifyPathSep(relFile); | ||
} | ||
return relFile; | ||
}); | ||
}); | ||
if (dir && unixify) { | ||
dir = helper.unixifyPathSep(dir); | ||
} | ||
return dir ? relative[dir] || [] : relative; | ||
}; | ||
// 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); | ||
// 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.lstatSync(dirfile).isDirectory()) { | ||
helper.objectPush(this._watched, dirname, dirfile + path.sep); | ||
} | ||
} | ||
} | ||
return this; | ||
}; | ||
// Remove file/dir from `watched` | ||
Gaze.prototype.remove = function(file) { | ||
platform.close(file); | ||
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); | ||
} | ||
return this; | ||
}; | ||
// Return watched files | ||
Gaze.prototype.watched = function(done) { | ||
done = nextback(done || function() {}); | ||
helper.flatToTree(platform.getWatchedPaths(), this.options.cwd, false, false, done); | ||
Gaze.prototype._unpollFile = function (file) { | ||
if (this._pollers[file]) { | ||
fs.unwatchFile(file, this._pollers[file]); | ||
delete this._pollers[file]; | ||
} | ||
return this; | ||
}; | ||
// 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._unpollDir = function (dir) { | ||
this._unpollFile(dir); | ||
for (var i = 0; i < this._watched[dir].length; i++) { | ||
this._unpollFile(this._watched[dir][i]); | ||
} | ||
if (typeof unixify === 'function') { | ||
done = unixify; | ||
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); | ||
} | ||
} | ||
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.lstatSync(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) { | ||
if (err) { | ||
self.emit('error', err); | ||
return; | ||
} | ||
// 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); | ||
}; |
@@ -1,9 +0,1 @@ | ||
/* | ||
* gaze | ||
* https://github.com/shama/gaze | ||
* | ||
* Copyright (c) 2014 Kyle Robinson Young | ||
* Licensed under the MIT license. | ||
*/ | ||
'use strict'; | ||
@@ -15,17 +7,25 @@ | ||
// Returns boolean whether filepath is dir terminated | ||
helper.isDir = function isDir(dir) { | ||
if (typeof dir !== 'string') { return false; } | ||
return (dir.slice(-(path.sep.length)) === path.sep) || (dir.slice(-1) === '/'); | ||
helper.isDir = function isDir (dir) { | ||
if (typeof dir !== 'string') { | ||
return false; | ||
} | ||
return (dir.slice(-(path.sep.length)) === path.sep); | ||
}; | ||
// Create a `key:[]` if doesnt exist on `obj` then push or concat the `val` | ||
helper.objectPush = function objectPush(obj, key, val) { | ||
if (obj[key] == null) { obj[key] = []; } | ||
if (Array.isArray(val)) { obj[key] = obj[key].concat(val); } | ||
else if (val) { obj[key].push(val); } | ||
return obj[key] = helper.unique(obj[key]); | ||
helper.objectPush = function objectPush (obj, key, val) { | ||
if (obj[key] == null) { | ||
obj[key] = []; | ||
} | ||
if (Array.isArray(val)) { | ||
obj[key] = obj[key].concat(val); | ||
} else if (val) { | ||
obj[key].push(val); | ||
} | ||
obj[key] = helper.unique(obj[key]); | ||
return obj[key]; | ||
}; | ||
// Ensures the dir is marked with path.sep | ||
helper.markDir = function markDir(dir) { | ||
helper.markDir = function markDir (dir) { | ||
if (typeof dir === 'string' && | ||
@@ -40,49 +40,6 @@ dir.slice(-(path.sep.length)) !== path.sep && | ||
// Changes path.sep to unix ones for testing | ||
helper.unixifyPathSep = function unixifyPathSep(filepath) { | ||
helper.unixifyPathSep = function unixifyPathSep (filepath) { | ||
return (process.platform === 'win32') ? String(filepath).replace(/\\/g, '/') : filepath; | ||
}; | ||
// 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); | ||
}); | ||
}; | ||
/** | ||
@@ -95,3 +52,12 @@ * Lo-Dash 1.0.1 <http://lodash.com/> | ||
*/ | ||
helper.unique = function unique() { var array = Array.prototype.concat.apply(Array.prototype, arguments); var result = []; for (var i = 0; i < array.length; i++) { if (result.indexOf(array[i]) === -1) { result.push(array[i]); } } return result; }; | ||
helper.unique = function unique () { | ||
var array = Array.prototype.concat.apply(Array.prototype, arguments); | ||
var result = []; | ||
for (var i = 0; i < array.length; i++) { | ||
if (result.indexOf(array[i]) === -1) { | ||
result.push(array[i]); | ||
} | ||
} | ||
return result; | ||
}; | ||
@@ -102,7 +68,4 @@ /** | ||
*/ | ||
helper.forEachSeries = function forEachSeries(arr, iterator, callback) { | ||
callback = callback || function () {}; | ||
if (!arr.length) { | ||
return callback(); | ||
} | ||
helper.forEachSeries = function forEachSeries (arr, iterator, callback) { | ||
if (!arr.length) { return callback(); } | ||
var completed = 0; | ||
@@ -114,6 +77,5 @@ var iterate = function () { | ||
callback = function () {}; | ||
} | ||
else { | ||
} else { | ||
completed += 1; | ||
if (completed >= arr.length) { | ||
if (completed === arr.length) { | ||
callback(null); | ||
@@ -120,0 +82,0 @@ } else { |
{ | ||
"name": "gaze", | ||
"description": "A globbing fs.watch wrapper built from the best parts of other fine watch libs.", | ||
"version": "0.6.4", | ||
"version": "1.0.0", | ||
"homepage": "https://github.com/shama/gaze", | ||
@@ -12,3 +12,3 @@ "author": { | ||
"type": "git", | ||
"url": "git://github.com/shama/gaze.git" | ||
"url": "https://github.com/shama/gaze.git" | ||
}, | ||
@@ -18,51 +18,39 @@ "bugs": { | ||
}, | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "https://github.com/shama/gaze/blob/master/LICENSE-MIT" | ||
} | ||
], | ||
"main": "index.js", | ||
"license": "MIT", | ||
"main": "lib/gaze", | ||
"engines": { | ||
"node": ">= 0.8.0" | ||
"node": ">= 0.10.0" | ||
}, | ||
"scripts": { | ||
"test": "grunt nodeunit -v", | ||
"install": "node-gyp rebuild" | ||
"test": "semistandard && grunt nodeunit -v" | ||
}, | ||
"dependencies": { | ||
"globule": "~0.2.0", | ||
"nextback": "~0.1.0", | ||
"bindings": "~1.2.0", | ||
"nan": "~0.8.0", | ||
"absolute-path": "0.0.0", | ||
"graceful-fs": "~2.0.3" | ||
"globule": "^0.2.0" | ||
}, | ||
"devDependencies": { | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-nodeunit": "~0.3.3", | ||
"grunt-contrib-jshint": "~0.9.2", | ||
"async": "^1.5.2", | ||
"grunt": "^0.4.5", | ||
"grunt-benchmark": "~0.2.0", | ||
"grunt-cli": "~0.1.13", | ||
"async": "~0.2.10", | ||
"rimraf": "~2.2.6", | ||
"ascii-table": "0.0.4" | ||
"grunt-contrib-jshint": "^1.0.0", | ||
"grunt-contrib-nodeunit": "^1.0.0", | ||
"rimraf": "^2.5.0", | ||
"semistandard": "^7.0.5" | ||
}, | ||
"keywords": [ | ||
"watch", | ||
"watcher", | ||
"watching", | ||
"fs.watch", | ||
"fswatcher", | ||
"fs", | ||
"glob", | ||
"utility" | ||
"glob" | ||
], | ||
"files": [ | ||
"index.js", | ||
"lib", | ||
"src", | ||
"binding.gyp", | ||
"AUTHORS", | ||
"LICENSE-MIT" | ||
] | ||
], | ||
"semistandard": { | ||
"ignore": [ | ||
"benchmarks", | ||
"experiments", | ||
"build", | ||
"test" | ||
] | ||
} | ||
} |
@@ -1,20 +0,10 @@ | ||
# 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) | ||
# gaze [![Build Status](http://img.shields.io/travis/shama/gaze.svg)](https://travis-ci.org/shama/gaze) [![Build status](https://ci.appveyor.com/api/projects/status/vtx65w9eg511tgo4)](https://ci.appveyor.com/project/shama/gaze) | ||
A globbing fs.watch wrapper built from the best parts of other fine watch libs. | ||
Compatible with Node.js 0.10/0.8, Windows, OSX and Linux. | ||
Compatible with Node.js 4.x/0.12/0.10, Windows, OSX and Linux. | ||
![gaze](http://dontkry.com/images/repos/gaze.png) | ||
## 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 | ||
@@ -33,5 +23,3 @@ Install the module with: `npm install gaze` or place into your `package.json` | ||
// Get all watched files | ||
this.watched(function(watched) { | ||
console.log(watched); | ||
}); | ||
var watched = this.watched(); | ||
@@ -59,5 +47,3 @@ // On file changed | ||
// Get watched files with relative paths | ||
this.relative(function(err, files) { | ||
console.log(files); | ||
}); | ||
var files = this.relative(); | ||
}); | ||
@@ -103,10 +89,2 @@ | ||
#### `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 | ||
@@ -141,3 +119,3 @@ | ||
* `debounceDelay` {integer} Delay for events called in succession for the same | ||
file/event | ||
file/event in milliseconds | ||
* `mode` {string} Force the watch mode. Either `'auto'` (default), `'watch'` (force native events), or `'poll'` (force stat polling). | ||
@@ -166,19 +144,13 @@ * `cwd` {string} The current working directory to base file patterns from. Default is `process.cwd()`. | ||
recurse directories. | ||
* `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. | ||
* `watched()` Returns the currently watched files. | ||
* `relative([dir, unixify])` 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)`. | ||
## FAQs | ||
## Similar Projects | ||
### Why Another `fs.watch` Wrapper? | ||
I liked parts of other `fs.watch` wrappers but none had all the features I | ||
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**). | ||
Other great watch libraries to try are: | ||
* [paulmillr's chokidar](https://github.com/paulmillr/chokidar) | ||
* [amasad's sane](https://github.com/amasad/sane) | ||
* [mikeal's watch](https://github.com/mikeal/watch) | ||
@@ -194,2 +166,3 @@ * [github's pathwatcher](https://github.com/atom/node-pathwatcher) | ||
## Release History | ||
* 1.0.0 - Revert back to 0.5.2. Drop support for Node.js v0.8. Fix for `maxListeners`. Update globule to `0.2.0`. | ||
* 0.6.4 - Catch and emit error from readdir (@oconnore). Fix for 0 maxListeners. Use graceful-fs to avoid EMFILE errors in other places fs is used. Better method to determine if pathwatcher was built. Fix keeping process alive too much, only init pathwatcher if a file is being watched. Set min required to Windows Vista when building on Windows (@pvolok). | ||
@@ -200,2 +173,3 @@ * 0.6.3 - Add support for node v0.11 | ||
* 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.2 - Fix for ENOENT error with non-existent symlinks [BACKPORTED]. | ||
* 0.5.1 - Use setImmediate (process.nextTick for node v0.8) to defer ready/nomatch events (@amasad). | ||
@@ -224,3 +198,3 @@ * 0.5.0 - Process is now kept alive while watching files. Emits a nomatch event when no files are matching. | ||
## License | ||
Copyright (c) 2014 Kyle Robinson Young | ||
Copyright (c) 2015 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
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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
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
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
0
24723
8
5
465
193
1
- Removedabsolute-path@0.0.0
- Removedbindings@~1.2.0
- Removedgraceful-fs@~2.0.3
- Removednan@~0.8.0
- Removednextback@~0.1.0
- Removedabsolute-path@0.0.0(transitive)
- Removedbindings@1.2.1(transitive)
- Removedgraceful-fs@2.0.3(transitive)
- Removednan@0.8.0(transitive)
- Removednextback@0.1.0(transitive)
Updatedglobule@^0.2.0