Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Exclusion Support to Gaze. Fixes GH-7 #21

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = function(grunt) {
}
},
nodeunit: {
files: ['test/**/*_test.js'],
files: ['test/**/*_test.js']
},
jshint: {
options: {
Expand Down
106 changes: 85 additions & 21 deletions lib/gaze.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
'use strict';

// libs
var _ = require('lodash');
var events = require('events');
var fs = require('fs');
var path = require('path');
var Glob = require('glob').Glob;
var minimatch = require('minimatch');
var Gaze, gaze;

// globals
// globalsP
var delay = 10;

// exports
Expand Down Expand Up @@ -86,7 +87,7 @@ function isString(obj) {

function isEmpty(obj) {
if (obj == null) { return true; }

if (Array.isArray(obj) || isString(obj)) { return obj.length === 0; }

for (var key in obj) {
Expand All @@ -100,7 +101,7 @@ function isEmpty(obj) {

function forEachSeries(arr, iterator, callback) {
if (!arr.length) { return callback(); }

var completed = 0;

var iterate = function() {
Expand All @@ -110,7 +111,7 @@ function forEachSeries(arr, iterator, callback) {
callback = function() {};
} else {
completed += 1;

if (completed === arr.length) {
callback(null);
} else {
Expand Down Expand Up @@ -155,7 +156,7 @@ function _unixifyPathSep(filepath) {
}

// `Gaze` EventEmitter object to return in the callback
Gaze = gaze.Gaze = __extends(function(files, opts, done) {
Gaze = gaze.Gaze = __extends(function(patterns, opts, done) {
var _this = this;

// If second arg is the callback
Expand Down Expand Up @@ -197,7 +198,7 @@ Gaze = gaze.Gaze = __extends(function(files, opts, done) {
}

// Initialize the watch on files
this.add(files, done);
this.add(patterns, done);

return this;
}, events.EventEmitter);
Expand Down Expand Up @@ -263,6 +264,9 @@ Gaze.prototype.close = function(_reset) {
});
if (_reset) {
_this._watched = Object.create(null);
_this._watchers = Object.create(null);
_this._patterns = [];
_this._cached = Object.create(null);
setTimeout(function() {
_this.emit('end');
_this.removeAllListeners();
Expand All @@ -271,23 +275,73 @@ Gaze.prototype.close = function(_reset) {
return _this;
};

// Add file patterns to be watched
Gaze.prototype.add = function(files, done) {
// Processing Patterns
// "Inspired from Grunt"
Gaze.prototype.processPatterns = function(patterns, processFn, done) {
var _this = this;
if (typeof files === 'string') {
files = [files];
}
this._patterns = union(this._patterns, files);
forEachSeries(files, function(pattern, next) {
if (isEmpty(pattern)) { return; }
_this._glob = new Glob(pattern, _this.options, function(err, files) {
var result = [];

patterns = _.flatten(patterns);

forEachSeries(patterns, function(pattern, next) {
processFn(pattern, function(err, matches) {
if (err) {
_this.emit('error', err);
return done(err);
}
_this._addToWatched(files);
var exclusion = pattern.indexOf('!') === 0;
if (exclusion) { pattern = pattern.slice(1); }
if (exclusion) {
//There has to be a better way to handle exclusion.
//Here we add the matched ! onto the result array, then diff them off
//If we didn't concat matches on first, we could end up adding our "excluded"
result = result.concat(matches);
result = _.difference(result, matches);
} else {
result = _.union(result, matches);
}
next();
});

}, function() {
_this._addToWatched(result);
return done(result);
});
};

Gaze.prototype._negationsLast = function(patterns) {
var result = [];
patterns.forEach(function(pattern) {
if (pattern.indexOf('!') === 0) {
result.push(pattern);
} else {
result.unshift(pattern);
}
});
return result;
};

// Add file patterns to be watched
Gaze.prototype.add = function(patterns, done) {
var _this = this;
if (typeof patterns === 'string') {
patterns = [patterns];
}
//if we don't want to mash our patterns with individual files that already matched
//an existing pattern, pass null to this function.
if (patterns != null) {
this._patterns = union(this._patterns, patterns);
this._patterns = this._negationsLast(this._patterns);
}

_this.processPatterns(this._patterns, function(pattern, done) {
if (_.isEmpty(pattern)) {return;}
_this._glob = new Glob(pattern, _this.options, function(err, files) {
if (err) {
done(err);
}
done(null, files);
});
}, function() {
_this.close(false);
_this._initWatched(done);
Expand Down Expand Up @@ -388,11 +442,20 @@ Gaze.prototype._addToWatched = function(files) {
// Returns true if the file matches this._patterns
Gaze.prototype._isMatch = function(file) {
var matched = false;
this._patterns.forEach(function(pattern) {
if (matched || (matched = minimatch(file, pattern)) ) {
return false;

//using for to allow early exit on first negation match.
//backwards because this will allow early exits if our negation match
for (var i = this._patterns.length - 1; i >= 0; i--) {
if (minimatch(file, this._patterns[i])) {
if (this._patterns[i].indexOf('!') === 0) {
//First time a negation is hit, we can hop out of the forEach.
matched = false;
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Just reading this to try to help out in reviewing.. If you don't mind.)
Did you mean break here?

Alternatively, you can use the Array.some() method.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hint on array.some, didn't know about it.

Yea, it should be a break. Oops :o

} else {
matched = true;
}
}
});
}
return matched;
};

Expand Down Expand Up @@ -434,6 +497,7 @@ Gaze.prototype._initWatched = function(done) {
var curWatched = Object.keys(_this._watched);
forEachSeries(curWatched, function(dir, next) {
var files = _this._watched[dir];

// Triggered when a watched dir has an event
_this._watchDir(dir, function(event, dirpath) {
var relDir = cwd === dir ? '.' : path.relative(cwd, dir);
Expand Down Expand Up @@ -477,7 +541,7 @@ Gaze.prototype._initWatched = function(done) {
var relFile = path.join(relDir, file);
if (_this._isMatch(relFile)) {
// Add to watch then emit event
_this.add(relFile, function() {
_this.add(null, function() {
_this.emit('added', path.join(dir, file));
});
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
},
"dependencies": {
"glob": "~3.1.14",
"minimatch": "~0.2.9"
"minimatch": "~0.2.9",
"lodash": "~1.0.0-rc.3"
},
"devDependencies": {
"grunt": "~0.4.0rc6",
Expand Down