'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 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; } } function shouldBeIgnored(path) { var positiveMatch = anymatch(positives, path, true); var negativeMatch = anymatch(negatives, 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; } var toWatch = positives.filter(exists); // 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)) { 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;