| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747 | 'use strict';var EventEmitter = require('events').EventEmitter;var fs = require('fs');var sysPath = require('path');var asyncEach = require('async-each');var anymatch = require('anymatch');var globParent = require('glob-parent');var isGlob = require('is-glob');var isAbsolute = require('path-is-absolute');var inherits = require('inherits');var braces = require('braces');var normalizePath = require('normalize-path');var upath = require('upath');var NodeFsHandler = require('./lib/nodefs-handler');var FsEventsHandler = require('./lib/fsevents-handler');var arrify = function(value) {  if (value == null) return [];  return Array.isArray(value) ? value : [value];};var flatten = function(list, result) {  if (result == null) result = [];  list.forEach(function(item) {    if (Array.isArray(item)) {      flatten(item, result);    } else {      result.push(item);    }  });  return result;};// Little isString util for use in Array#every.var isString = function(thing) {  return typeof thing === 'string';};// Public: Main class.// Watches files & directories for changes.//// * _opts - object, chokidar options hash//// Emitted events:// `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`//// Examples////  var watcher = new FSWatcher()//    .add(directories)//    .on('add', path => console.log('File', path, 'was added'))//    .on('change', path => console.log('File', path, 'was changed'))//    .on('unlink', path => console.log('File', path, 'was removed'))//    .on('all', (event, path) => console.log(path, ' emitted ', event))//function FSWatcher(_opts) {  EventEmitter.call(this);  var opts = {};  // in case _opts that is passed in is a frozen object  if (_opts) for (var opt in _opts) opts[opt] = _opts[opt];  this._watched = Object.create(null);  this._closers = Object.create(null);  this._ignoredPaths = Object.create(null);  Object.defineProperty(this, '_globIgnored', {    get: function() { return Object.keys(this._ignoredPaths); }  });  this.closed = false;  this._throttled = Object.create(null);  this._symlinkPaths = Object.create(null);  function undef(key) {    return opts[key] === undefined;  }  // Set up default options.  if (undef('persistent')) opts.persistent = true;  if (undef('ignoreInitial')) opts.ignoreInitial = false;  if (undef('ignorePermissionErrors')) opts.ignorePermissionErrors = false;  if (undef('interval')) opts.interval = 100;  if (undef('binaryInterval')) opts.binaryInterval = 300;  if (undef('disableGlobbing')) opts.disableGlobbing = false;  this.enableBinaryInterval = opts.binaryInterval !== opts.interval;  // Enable fsevents on OS X when polling isn't explicitly enabled.  if (undef('useFsEvents')) opts.useFsEvents = !opts.usePolling;  // If we can't use fsevents, ensure the options reflect it's disabled.  if (!FsEventsHandler.canUse()) opts.useFsEvents = false;  // Use polling on Mac if not using fsevents.  // Other platforms use non-polling fs.watch.  if (undef('usePolling') && !opts.useFsEvents) {    opts.usePolling = process.platform === 'darwin';  }  // Global override (useful for end-developers that need to force polling for all  // instances of chokidar, regardless of usage/dependency depth)  var envPoll = process.env.CHOKIDAR_USEPOLLING;  if (envPoll !== undefined) {    var envLower = envPoll.toLowerCase();    if (envLower === 'false' || envLower === '0') {      opts.usePolling = false;    } else if (envLower === 'true' || envLower === '1') {      opts.usePolling = true;    } else {      opts.usePolling = !!envLower    }  }  var envInterval = process.env.CHOKIDAR_INTERVAL;  if (envInterval) {    opts.interval = parseInt(envInterval);  }  // Editor atomic write normalization enabled by default with fs.watch  if (undef('atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents;  if (opts.atomic) this._pendingUnlinks = Object.create(null);  if (undef('followSymlinks')) opts.followSymlinks = true;  if (undef('awaitWriteFinish')) opts.awaitWriteFinish = false;  if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {};  var awf = opts.awaitWriteFinish;  if (awf) {    if (!awf.stabilityThreshold) awf.stabilityThreshold = 2000;    if (!awf.pollInterval) awf.pollInterval = 100;    this._pendingWrites = Object.create(null);  }  if (opts.ignored) opts.ignored = arrify(opts.ignored);  this._isntIgnored = function(path, stat) {    return !this._isIgnored(path, stat);  }.bind(this);  var readyCalls = 0;  this._emitReady = function() {    if (++readyCalls >= this._readyCount) {      this._emitReady = Function.prototype;      this._readyEmitted = true;      // use process.nextTick to allow time for listener to be bound      process.nextTick(this.emit.bind(this, 'ready'));    }  }.bind(this);  this.options = opts;  // You’re frozen when your heart’s not open.  Object.freeze(opts);}inherits(FSWatcher, EventEmitter);// Common helpers// --------------// Private method: Normalize and emit events//// * event     - string, type of event// * path      - string, file or directory path// * val[1..3] - arguments to be passed with event//// Returns the error if defined, otherwise the value of the// FSWatcher instance's `closed` flagFSWatcher.prototype._emit = function(event, path, val1, val2, val3) {  if (this.options.cwd) path = sysPath.relative(this.options.cwd, path);  var args = [event, path];  if (val3 !== undefined) args.push(val1, val2, val3);  else if (val2 !== undefined) args.push(val1, val2);  else if (val1 !== undefined) args.push(val1);  var awf = this.options.awaitWriteFinish;  if (awf && this._pendingWrites[path]) {    this._pendingWrites[path].lastChange = new Date();    return this;  }  if (this.options.atomic) {    if (event === 'unlink') {      this._pendingUnlinks[path] = args;      setTimeout(function() {        Object.keys(this._pendingUnlinks).forEach(function(path) {          this.emit.apply(this, this._pendingUnlinks[path]);          this.emit.apply(this, ['all'].concat(this._pendingUnlinks[path]));          delete this._pendingUnlinks[path];        }.bind(this));      }.bind(this), typeof this.options.atomic === "number"        ? this.options.atomic        : 100);      return this;    } else if (event === 'add' && this._pendingUnlinks[path]) {      event = args[0] = 'change';      delete this._pendingUnlinks[path];    }  }  var emitEvent = function() {    this.emit.apply(this, args);    if (event !== 'error') this.emit.apply(this, ['all'].concat(args));  }.bind(this);  if (awf && (event === 'add' || event === 'change') && this._readyEmitted) {    var awfEmit = function(err, stats) {      if (err) {        event = args[0] = 'error';        args[1] = err;        emitEvent();      } else if (stats) {        // if stats doesn't exist the file must have been deleted        if (args.length > 2) {          args[2] = stats;        } else {          args.push(stats);        }        emitEvent();      }    };    this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);    return this;  }  if (event === 'change') {    if (!this._throttle('change', path, 50)) return this;  }  if (    this.options.alwaysStat && val1 === undefined &&    (event === 'add' || event === 'addDir' || event === 'change')  ) {    var fullPath = this.options.cwd ? sysPath.join(this.options.cwd, path) : path;    fs.stat(fullPath, function(error, stats) {      // Suppress event when fs.stat fails, to avoid sending undefined 'stat'      if (error || !stats) return;      args.push(stats);      emitEvent();    });  } else {    emitEvent();  }  return this;};// Private method: Common handler for errors//// * error  - object, Error instance//// Returns the error if defined, otherwise the value of the// FSWatcher instance's `closed` flagFSWatcher.prototype._handleError = function(error) {  var code = error && error.code;  var ipe = this.options.ignorePermissionErrors;  if (error &&    code !== 'ENOENT' &&    code !== 'ENOTDIR' &&    (!ipe || (code !== 'EPERM' && code !== 'EACCES'))  ) this.emit('error', error);  return error || this.closed;};// Private method: Helper utility for throttling//// * action  - string, type of action being throttled// * path    - string, path being acted upon// * timeout - int, duration of time to suppress duplicate actions//// Returns throttle tracking object or false if action should be suppressedFSWatcher.prototype._throttle = function(action, path, timeout) {  if (!(action in this._throttled)) {    this._throttled[action] = Object.create(null);  }  var throttled = this._throttled[action];  if (path in throttled) {    throttled[path].count++;    return false;  }  function clear() {    var count = throttled[path] ? throttled[path].count : 0;    delete throttled[path];    clearTimeout(timeoutObject);    return count;  }  var timeoutObject = setTimeout(clear, timeout);  throttled[path] = {timeoutObject: timeoutObject, clear: clear, count: 0};  return throttled[path];};// Private method: Awaits write operation to finish//// * path    - string, path being acted upon// * threshold - int, time in milliseconds a file size must be fixed before//                    acknowledging write operation is finished// * awfEmit - function, to be called when ready for event to be emitted// Polls a newly created file for size variations. When files size does not// change for 'threshold' milliseconds calls callback.FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit) {  var timeoutHandler;  var fullPath = path;  if (this.options.cwd && !isAbsolute(path)) {    fullPath = sysPath.join(this.options.cwd, path);  }  var now = new Date();  var awaitWriteFinish = (function (prevStat) {    fs.stat(fullPath, function(err, curStat) {      if (err || !(path in this._pendingWrites)) {        if (err && err.code !== 'ENOENT') awfEmit(err);        return;      }      var now = new Date();      if (prevStat && curStat.size != prevStat.size) {        this._pendingWrites[path].lastChange = now;      }      if (now - this._pendingWrites[path].lastChange >= threshold) {        delete this._pendingWrites[path];        awfEmit(null, curStat);      } else {        timeoutHandler = setTimeout(          awaitWriteFinish.bind(this, curStat),          this.options.awaitWriteFinish.pollInterval        );      }    }.bind(this));  }.bind(this));  if (!(path in this._pendingWrites)) {    this._pendingWrites[path] = {      lastChange: now,      cancelWait: function() {        delete this._pendingWrites[path];        clearTimeout(timeoutHandler);        return event;      }.bind(this)    };    timeoutHandler = setTimeout(      awaitWriteFinish.bind(this),      this.options.awaitWriteFinish.pollInterval    );  }};// Private method: Determines whether user has asked to ignore this path//// * path  - string, path to file or directory// * stats - object, result of fs.stat//// Returns booleanvar dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/;FSWatcher.prototype._isIgnored = function(path, stats) {  if (this.options.atomic && dotRe.test(path)) return true;  if (!this._userIgnored) {    var cwd = this.options.cwd;    var ignored = this.options.ignored;    if (cwd && ignored) {      ignored = ignored.map(function (path) {        if (typeof path !== 'string') return path;        return upath.normalize(isAbsolute(path) ? path : sysPath.join(cwd, path));      });    }    var paths = arrify(ignored)      .filter(function(path) {        return typeof path === 'string' && !isGlob(path);      }).map(function(path) {        return path + '/**';      });    this._userIgnored = anymatch(      this._globIgnored.concat(ignored).concat(paths)    );  }  return this._userIgnored([path, stats]);};// Private method: Provides a set of common helpers and properties relating to// symlink and glob handling//// * path - string, file, directory, or glob pattern being watched// * depth - int, at any depth > 0, this isn't a glob//// Returns object containing helpers for this pathvar replacerRe = /^\.[\/\\]/;FSWatcher.prototype._getWatchHelpers = function(path, depth) {  path = path.replace(replacerRe, '');  var watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path);  var fullWatchPath = sysPath.resolve(watchPath);  var hasGlob = watchPath !== path;  var globFilter = hasGlob ? anymatch(path) : false;  var follow = this.options.followSymlinks;  var globSymlink = hasGlob && follow ? null : false;  var checkGlobSymlink = function(entry) {    // only need to resolve once    // first entry should always have entry.parentDir === ''    if (globSymlink == null) {      globSymlink = entry.fullParentDir === fullWatchPath ? false : {        realPath: entry.fullParentDir,        linkPath: fullWatchPath      };    }    if (globSymlink) {      return entry.fullPath.replace(globSymlink.realPath, globSymlink.linkPath);    }    return entry.fullPath;  };  var entryPath = function(entry) {    return sysPath.join(watchPath,      sysPath.relative(watchPath, checkGlobSymlink(entry))    );  };  var filterPath = function(entry) {    if (entry.stat && entry.stat.isSymbolicLink()) return filterDir(entry);    var resolvedPath = entryPath(entry);    return (!hasGlob || globFilter(resolvedPath)) &&      this._isntIgnored(resolvedPath, entry.stat) &&      (this.options.ignorePermissionErrors ||        this._hasReadPermissions(entry.stat));  }.bind(this);  var getDirParts = function(path) {    if (!hasGlob) return false;    var parts = [];    var expandedPath = braces.expand(path);    expandedPath.forEach(function(path) {      parts.push(sysPath.relative(watchPath, path).split(/[\/\\]/));    });    return parts;  };  var dirParts = getDirParts(path);  if (dirParts) {    dirParts.forEach(function(parts) {      if (parts.length > 1) parts.pop();    });  }  var unmatchedGlob;  var filterDir = function(entry) {    if (hasGlob) {      var entryParts = getDirParts(checkGlobSymlink(entry));      var globstar = false;      unmatchedGlob = !dirParts.some(function(parts) {        return parts.every(function(part, i) {          if (part === '**') globstar = true;          return globstar || !entryParts[0][i] || anymatch(part, entryParts[0][i]);        });      });    }    return !unmatchedGlob && this._isntIgnored(entryPath(entry), entry.stat);  }.bind(this);  return {    followSymlinks: follow,    statMethod: follow ? 'stat' : 'lstat',    path: path,    watchPath: watchPath,    entryPath: entryPath,    hasGlob: hasGlob,    globFilter: globFilter,    filterPath: filterPath,    filterDir: filterDir  };};// Directory helpers// -----------------// Private method: Provides directory tracking objects//// * directory - string, path of the directory//// Returns the directory's tracking objectFSWatcher.prototype._getWatchedDir = function(directory) {  var dir = sysPath.resolve(directory);  var watcherRemove = this._remove.bind(this);  if (!(dir in this._watched)) this._watched[dir] = {    _items: Object.create(null),    add: function(item) {      if (item !== '.' && item !== '..') this._items[item] = true;    },    remove: function(item) {      delete this._items[item];      if (!this.children().length) {        fs.readdir(dir, function(err) {          if (err) watcherRemove(sysPath.dirname(dir), sysPath.basename(dir));        });      }    },    has: function(item) {return item in this._items;},    children: function() {return Object.keys(this._items);}  };  return this._watched[dir];};// File helpers// ------------// Private method: Check for read permissions// Based on this answer on SO: http://stackoverflow.com/a/11781404/1358405//// * stats - object, result of fs.stat//// Returns booleanFSWatcher.prototype._hasReadPermissions = function(stats) {  return Boolean(4 & parseInt(((stats && stats.mode) & 0x1ff).toString(8)[0], 10));};// Private method: Handles emitting unlink events for// files and directories, and via recursion, for// files and directories within directories that are unlinked//// * directory - string, directory within which the following item is located// * item      - string, base path of item/directory//// Returns nothingFSWatcher.prototype._remove = function(directory, item) {  // if what is being deleted is a directory, get that directory's paths  // for recursive deleting and cleaning of watched object  // if it is not a directory, nestedDirectoryChildren will be empty array  var path = sysPath.join(directory, item);  var fullPath = sysPath.resolve(path);  var isDirectory = this._watched[path] || this._watched[fullPath];  // prevent duplicate handling in case of arriving here nearly simultaneously  // via multiple paths (such as _handleFile and _handleDir)  if (!this._throttle('remove', path, 100)) return;  // if the only watched file is removed, watch for its return  var watchedDirs = Object.keys(this._watched);  if (!isDirectory && !this.options.useFsEvents && watchedDirs.length === 1) {    this.add(directory, item, true);  }  // This will create a new entry in the watched object in either case  // so we got to do the directory check beforehand  var nestedDirectoryChildren = this._getWatchedDir(path).children();  // Recursively remove children directories / files.  nestedDirectoryChildren.forEach(function(nestedItem) {    this._remove(path, nestedItem);  }, this);  // Check if item was on the watched list and remove it  var parent = this._getWatchedDir(directory);  var wasTracked = parent.has(item);  parent.remove(item);  // If we wait for this file to be fully written, cancel the wait.  var relPath = path;  if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path);  if (this.options.awaitWriteFinish && this._pendingWrites[relPath]) {    var event = this._pendingWrites[relPath].cancelWait();    if (event === 'add') return;  }  // The Entry will either be a directory that just got removed  // or a bogus entry to a file, in either case we have to remove it  delete this._watched[path];  delete this._watched[fullPath];  var eventName = isDirectory ? 'unlinkDir' : 'unlink';  if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path);  // Avoid conflicts if we later create another file with the same name  if (!this.options.useFsEvents) {    this._closePath(path);  }};FSWatcher.prototype._closePath = function(path) {  if (!this._closers[path]) return;  this._closers[path].forEach(function(closer) {    closer();  });  delete this._closers[path];  this._getWatchedDir(sysPath.dirname(path)).remove(sysPath.basename(path));}// Public method: Adds paths to be watched on an existing FSWatcher instance// * paths     - string or array of strings, file/directory paths and/or globs// * _origAdd  - private boolean, for handling non-existent paths to be watched// * _internal - private boolean, indicates a non-user add// Returns an instance of FSWatcher for chaining.FSWatcher.prototype.add = function(paths, _origAdd, _internal) {  var disableGlobbing = this.options.disableGlobbing;  var cwd = this.options.cwd;  this.closed = false;  paths = flatten(arrify(paths));  if (!paths.every(isString)) {    throw new TypeError('Non-string provided as watch path: ' + paths);  }  if (cwd) paths = paths.map(function(path) {    var absPath;    if (isAbsolute(path)) {      absPath = path;    } else if (path[0] === '!') {      absPath = '!' + sysPath.join(cwd, path.substring(1));    } else {      absPath = sysPath.join(cwd, path);    }    // Check `path` instead of `absPath` because the cwd portion can't be a glob    if (disableGlobbing || !isGlob(path)) {      return absPath;    } else {      return normalizePath(absPath);    }  });  // set aside negated glob strings  paths = paths.filter(function(path) {    if (path[0] === '!') {      this._ignoredPaths[path.substring(1)] = true;    } else {      // if a path is being added that was previously ignored, stop ignoring it      delete this._ignoredPaths[path];      delete this._ignoredPaths[path + '/**'];      // reset the cached userIgnored anymatch fn      // to make ignoredPaths changes effective      this._userIgnored = null;      return true;    }  }, this);  if (this.options.useFsEvents && FsEventsHandler.canUse()) {    if (!this._readyCount) this._readyCount = paths.length;    if (this.options.persistent) this._readyCount *= 2;    paths.forEach(this._addToFsEvents, this);  } else {    if (!this._readyCount) this._readyCount = 0;    this._readyCount += paths.length;    asyncEach(paths, function(path, next) {      this._addToNodeFs(path, !_internal, 0, 0, _origAdd, function(err, res) {        if (res) this._emitReady();        next(err, res);      }.bind(this));    }.bind(this), function(error, results) {      results.forEach(function(item) {        if (!item || this.closed) return;        this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));      }, this);    }.bind(this));  }  return this;};// Public method: Close watchers or start ignoring events from specified paths.// * paths     - string or array of strings, file/directory paths and/or globs// Returns instance of FSWatcher for chaining.FSWatcher.prototype.unwatch = function(paths) {  if (this.closed) return this;  paths = flatten(arrify(paths));  paths.forEach(function(path) {    // convert to absolute path unless relative path already matches    if (!isAbsolute(path) && !this._closers[path]) {      if (this.options.cwd) path = sysPath.join(this.options.cwd, path);      path = sysPath.resolve(path);    }    this._closePath(path);    this._ignoredPaths[path] = true;    if (path in this._watched) {      this._ignoredPaths[path + '/**'] = true;    }    // reset the cached userIgnored anymatch fn    // to make ignoredPaths changes effective    this._userIgnored = null;  }, this);  return this;};// Public method: Close watchers and remove all listeners from watched paths.// Returns instance of FSWatcher for chaining.FSWatcher.prototype.close = function() {  if (this.closed) return this;  this.closed = true;  Object.keys(this._closers).forEach(function(watchPath) {    this._closers[watchPath].forEach(function(closer) {      closer();    });    delete this._closers[watchPath];  }, this);  this._watched = Object.create(null);  this.removeAllListeners();  return this;};// Public method: Expose list of watched paths// Returns object w/ dir paths as keys and arrays of contained paths as values.FSWatcher.prototype.getWatched = function() {  var watchList = {};  Object.keys(this._watched).forEach(function(dir) {    var key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir;    watchList[key || '.'] = Object.keys(this._watched[dir]._items).sort();  }.bind(this));  return watchList;};// Attach watch handler prototype methodsfunction importHandler(handler) {  Object.keys(handler.prototype).forEach(function(method) {    FSWatcher.prototype[method] = handler.prototype[method];  });}importHandler(NodeFsHandler);if (FsEventsHandler.canUse()) importHandler(FsEventsHandler);// Export FSWatcher classexports.FSWatcher = FSWatcher;// Public function: Instantiates watcher with paths to be tracked.// * paths     - string or array of strings, file/directory paths and/or globs// * options   - object, chokidar options// Returns an instance of FSWatcher for chaining.exports.watch = function(paths, options) {  return new FSWatcher(options).add(paths);};
 |