You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
3.4 KiB
149 lines
3.4 KiB
'use strict'; |
|
|
|
var chokidar = require('chokidar'); |
|
var debounce = require('just-debounce'); |
|
var asyncDone = require('async-done'); |
|
var defaults = require('object.defaults/immutable'); |
|
var isNegatedGlob = require('is-negated-glob'); |
|
var anymatch = require('anymatch'); |
|
var normalize = require('normalize-path'); |
|
|
|
var defaultOpts = { |
|
delay: 200, |
|
events: ['add', 'change', 'unlink'], |
|
ignored: [], |
|
ignoreInitial: true, |
|
queue: true, |
|
}; |
|
|
|
function listenerCount(ee, evtName) { |
|
if (typeof ee.listenerCount === 'function') { |
|
return ee.listenerCount(evtName); |
|
} |
|
|
|
return ee.listeners(evtName).length; |
|
} |
|
|
|
function hasErrorListener(ee) { |
|
return listenerCount(ee, 'error') !== 0; |
|
} |
|
|
|
function exists(val) { |
|
return val != null; |
|
} |
|
|
|
function watch(glob, options, cb) { |
|
if (typeof options === 'function') { |
|
cb = options; |
|
options = {}; |
|
} |
|
|
|
var opt = defaults(options, defaultOpts); |
|
|
|
if (!Array.isArray(opt.events)) { |
|
opt.events = [opt.events]; |
|
} |
|
|
|
if (Array.isArray(glob)) { |
|
// We slice so we don't mutate the passed globs array |
|
glob = glob.slice(); |
|
} else { |
|
glob = [glob]; |
|
} |
|
|
|
var queued = false; |
|
var running = false; |
|
|
|
// These use sparse arrays to keep track of the index in the |
|
// original globs array |
|
var positives = new Array(glob.length); |
|
var negatives = new Array(glob.length); |
|
|
|
// Reverse the glob here so we don't end up with a positive |
|
// and negative glob in position 0 after a reverse |
|
glob.reverse().forEach(sortGlobs); |
|
|
|
function sortGlobs(globString, index) { |
|
var result = isNegatedGlob(globString); |
|
if (result.negated) { |
|
negatives[index] = result.pattern; |
|
} else { |
|
positives[index] = result.pattern; |
|
} |
|
} |
|
|
|
var toWatch = positives.filter(exists); |
|
|
|
function joinCwd(glob) { |
|
if (glob && opt.cwd) { |
|
return normalize(opt.cwd + '/' + glob); |
|
} |
|
|
|
return glob; |
|
} |
|
|
|
// We only do add our custom `ignored` if there are some negative globs |
|
// TODO: I'm not sure how to test this |
|
if (negatives.some(exists)) { |
|
var normalizedPositives = positives.map(joinCwd); |
|
var normalizedNegatives = negatives.map(joinCwd); |
|
var shouldBeIgnored = function(path) { |
|
var positiveMatch = anymatch(normalizedPositives, path, true); |
|
var negativeMatch = anymatch(normalizedNegatives, path, true); |
|
// If negativeMatch is -1, that means it was never negated |
|
if (negativeMatch === -1) { |
|
return false; |
|
} |
|
|
|
// If the negative is "less than" the positive, that means |
|
// it came later in the glob array before we reversed them |
|
return negativeMatch < positiveMatch; |
|
}; |
|
|
|
opt.ignored = [].concat(opt.ignored, shouldBeIgnored); |
|
} |
|
var watcher = chokidar.watch(toWatch, opt); |
|
|
|
function runComplete(err) { |
|
running = false; |
|
|
|
if (err && hasErrorListener(watcher)) { |
|
watcher.emit('error', err); |
|
} |
|
|
|
// If we have a run queued, start onChange again |
|
if (queued) { |
|
queued = false; |
|
onChange(); |
|
} |
|
} |
|
|
|
function onChange() { |
|
if (running) { |
|
if (opt.queue) { |
|
queued = true; |
|
} |
|
return; |
|
} |
|
|
|
running = true; |
|
asyncDone(cb, runComplete); |
|
} |
|
|
|
var fn; |
|
if (typeof cb === 'function') { |
|
fn = debounce(onChange, opt.delay); |
|
} |
|
|
|
function watchEvent(eventName) { |
|
watcher.on(eventName, fn); |
|
} |
|
|
|
if (fn) { |
|
opt.events.forEach(watchEvent); |
|
} |
|
|
|
return watcher; |
|
} |
|
|
|
module.exports = watch;
|
|
|