CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
index.js974 linesDownload Raw Back to chokidar
1'use strict';2 3const { EventEmitter } = require('events');4const fs = require('fs');5const sysPath = require('path');6const { promisify } = require('util');7const readdirp = require('readdirp');8const anymatch = require('anymatch').default;9const globParent = require('glob-parent');10const isGlob = require('is-glob');11const braces = require('braces');12const normalizePath = require('normalize-path');13 14const NodeFsHandler = require('./lib/nodefs-handler');15const FsEventsHandler = require('./lib/fsevents-handler');16const {17  EV_ALL,18  EV_READY,19  EV_ADD,20  EV_CHANGE,21  EV_UNLINK,22  EV_ADD_DIR,23  EV_UNLINK_DIR,24  EV_RAW,25  EV_ERROR,26 27  STR_CLOSE,28  STR_END,29 30  BACK_SLASH_RE,31  DOUBLE_SLASH_RE,32  SLASH_OR_BACK_SLASH_RE,33  DOT_RE,34  REPLACER_RE,35 36  SLASH,37  SLASH_SLASH,38  BRACE_START,39  BANG,40  ONE_DOT,41  TWO_DOTS,42  GLOBSTAR,43  SLASH_GLOBSTAR,44  ANYMATCH_OPTS,45  STRING_TYPE,46  FUNCTION_TYPE,47  EMPTY_STR,48  EMPTY_FN,49 50  isWindows,51  isMacos,52  isIBMi53} = require('./lib/constants');54 55const stat = promisify(fs.stat);56const readdir = promisify(fs.readdir);57 58/**59 * @typedef {String} Path60 * @typedef {'all'|'add'|'addDir'|'change'|'unlink'|'unlinkDir'|'raw'|'error'|'ready'} EventName61 * @typedef {'readdir'|'watch'|'add'|'remove'|'change'} ThrottleType62 */63 64/**65 *66 * @typedef {Object} WatchHelpers67 * @property {Boolean} followSymlinks68 * @property {'stat'|'lstat'} statMethod69 * @property {Path} path70 * @property {Path} watchPath71 * @property {Function} entryPath72 * @property {Boolean} hasGlob73 * @property {Object} globFilter74 * @property {Function} filterPath75 * @property {Function} filterDir76 */77 78const arrify = (value = []) => Array.isArray(value) ? value : [value];79const flatten = (list, result = []) => {80  list.forEach(item => {81    if (Array.isArray(item)) {82      flatten(item, result);83    } else {84      result.push(item);85    }86  });87  return result;88};89 90const unifyPaths = (paths_) => {91  /**92   * @type {Array<String>}93   */94  const paths = flatten(arrify(paths_));95  if (!paths.every(p => typeof p === STRING_TYPE)) {96    throw new TypeError(`Non-string provided as watch path: ${paths}`);97  }98  return paths.map(normalizePathToUnix);99};100 101// If SLASH_SLASH occurs at the beginning of path, it is not replaced102//     because "//StoragePC/DrivePool/Movies" is a valid network path103const toUnix = (string) => {104  let str = string.replace(BACK_SLASH_RE, SLASH);105  let prepend = false;106  if (str.startsWith(SLASH_SLASH)) {107    prepend = true;108  }109  while (str.match(DOUBLE_SLASH_RE)) {110    str = str.replace(DOUBLE_SLASH_RE, SLASH);111  }112  if (prepend) {113    str = SLASH + str;114  }115  return str;116};117 118// Our version of upath.normalize119// TODO: this is not equal to path-normalize module - investigate why120const normalizePathToUnix = (path) => toUnix(sysPath.normalize(toUnix(path)));121 122const normalizeIgnored = (cwd = EMPTY_STR) => (path) => {123  if (typeof path !== STRING_TYPE) return path;124  return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path));125};126 127const getAbsolutePath = (path, cwd) => {128  if (sysPath.isAbsolute(path)) {129    return path;130  }131  if (path.startsWith(BANG)) {132    return BANG + sysPath.join(cwd, path.slice(1));133  }134  return sysPath.join(cwd, path);135};136 137const undef = (opts, key) => opts[key] === undefined;138 139/**140 * Directory entry.141 * @property {Path} path142 * @property {Set<Path>} items143 */144class DirEntry {145  /**146   * @param {Path} dir147   * @param {Function} removeWatcher148   */149  constructor(dir, removeWatcher) {150    this.path = dir;151    this._removeWatcher = removeWatcher;152    /** @type {Set<Path>} */153    this.items = new Set();154  }155 156  add(item) {157    const {items} = this;158    if (!items) return;159    if (item !== ONE_DOT && item !== TWO_DOTS) items.add(item);160  }161 162  async remove(item) {163    const {items} = this;164    if (!items) return;165    items.delete(item);166    if (items.size > 0) return;167 168    const dir = this.path;169    try {170      await readdir(dir);171    } catch (err) {172      if (this._removeWatcher) {173        this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir));174      }175    }176  }177 178  has(item) {179    const {items} = this;180    if (!items) return;181    return items.has(item);182  }183 184  /**185   * @returns {Array<String>}186   */187  getChildren() {188    const {items} = this;189    if (!items) return;190    return [...items.values()];191  }192 193  dispose() {194    this.items.clear();195    delete this.path;196    delete this._removeWatcher;197    delete this.items;198    Object.freeze(this);199  }200}201 202const STAT_METHOD_F = 'stat';203const STAT_METHOD_L = 'lstat';204class WatchHelper {205  constructor(path, watchPath, follow, fsw) {206    this.fsw = fsw;207    this.path = path = path.replace(REPLACER_RE, EMPTY_STR);208    this.watchPath = watchPath;209    this.fullWatchPath = sysPath.resolve(watchPath);210    this.hasGlob = watchPath !== path;211    /** @type {object|boolean} */212    if (path === EMPTY_STR) this.hasGlob = false;213    this.globSymlink = this.hasGlob && follow ? undefined : false;214    this.globFilter = this.hasGlob ? anymatch(path, undefined, ANYMATCH_OPTS) : false;215    this.dirParts = this.getDirParts(path);216    this.dirParts.forEach((parts) => {217      if (parts.length > 1) parts.pop();218    });219    this.followSymlinks = follow;220    this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;221  }222 223  checkGlobSymlink(entry) {224    // only need to resolve once225    // first entry should always have entry.parentDir === EMPTY_STR226    if (this.globSymlink === undefined) {227      this.globSymlink = entry.fullParentDir === this.fullWatchPath ?228        false : {realPath: entry.fullParentDir, linkPath: this.fullWatchPath};229    }230 231    if (this.globSymlink) {232      return entry.fullPath.replace(this.globSymlink.realPath, this.globSymlink.linkPath);233    }234 235    return entry.fullPath;236  }237 238  entryPath(entry) {239    return sysPath.join(this.watchPath,240      sysPath.relative(this.watchPath, this.checkGlobSymlink(entry))241    );242  }243 244  filterPath(entry) {245    const {stats} = entry;246    if (stats && stats.isSymbolicLink()) return this.filterDir(entry);247    const resolvedPath = this.entryPath(entry);248    const matchesGlob = this.hasGlob && typeof this.globFilter === FUNCTION_TYPE ?249      this.globFilter(resolvedPath) : true;250    return matchesGlob &&251      this.fsw._isntIgnored(resolvedPath, stats) &&252      this.fsw._hasReadPermissions(stats);253  }254 255  getDirParts(path) {256    if (!this.hasGlob) return [];257    const parts = [];258    const expandedPath = path.includes(BRACE_START) ? braces.expand(path) : [path];259    expandedPath.forEach((path) => {260      parts.push(sysPath.relative(this.watchPath, path).split(SLASH_OR_BACK_SLASH_RE));261    });262    return parts;263  }264 265  filterDir(entry) {266    if (this.hasGlob) {267      const entryParts = this.getDirParts(this.checkGlobSymlink(entry));268      let globstar = false;269      this.unmatchedGlob = !this.dirParts.some((parts) => {270        return parts.every((part, i) => {271          if (part === GLOBSTAR) globstar = true;272          return globstar || !entryParts[0][i] || anymatch(part, entryParts[0][i], ANYMATCH_OPTS);273        });274      });275    }276    return !this.unmatchedGlob && this.fsw._isntIgnored(this.entryPath(entry), entry.stats);277  }278}279 280/**281 * Watches files & directories for changes. Emitted events:282 * `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`283 *284 *     new FSWatcher()285 *       .add(directories)286 *       .on('add', path => log('File', path, 'was added'))287 */288class FSWatcher extends EventEmitter {289// Not indenting methods for history sake; for now.290constructor(_opts) {291  super();292 293  const opts = {};294  if (_opts) Object.assign(opts, _opts); // for frozen objects295 296  /** @type {Map<String, DirEntry>} */297  this._watched = new Map();298  /** @type {Map<String, Array>} */299  this._closers = new Map();300  /** @type {Set<String>} */301  this._ignoredPaths = new Set();302 303  /** @type {Map<ThrottleType, Map>} */304  this._throttled = new Map();305 306  /** @type {Map<Path, String|Boolean>} */307  this._symlinkPaths = new Map();308 309  this._streams = new Set();310  this.closed = false;311 312  // Set up default options.313  if (undef(opts, 'persistent')) opts.persistent = true;314  if (undef(opts, 'ignoreInitial')) opts.ignoreInitial = false;315  if (undef(opts, 'ignorePermissionErrors')) opts.ignorePermissionErrors = false;316  if (undef(opts, 'interval')) opts.interval = 100;317  if (undef(opts, 'binaryInterval')) opts.binaryInterval = 300;318  if (undef(opts, 'disableGlobbing')) opts.disableGlobbing = false;319  opts.enableBinaryInterval = opts.binaryInterval !== opts.interval;320 321  // Enable fsevents on OS X when polling isn't explicitly enabled.322  if (undef(opts, 'useFsEvents')) opts.useFsEvents = !opts.usePolling;323 324  // If we can't use fsevents, ensure the options reflect it's disabled.325  const canUseFsEvents = FsEventsHandler.canUse();326  if (!canUseFsEvents) opts.useFsEvents = false;327 328  // Use polling on Mac if not using fsevents.329  // Other platforms use non-polling fs_watch.330  if (undef(opts, 'usePolling') && !opts.useFsEvents) {331    opts.usePolling = isMacos;332  }333 334  // Always default to polling on IBM i because fs.watch() is not available on IBM i.335  if(isIBMi) {336    opts.usePolling = true;337  }338 339  // Global override (useful for end-developers that need to force polling for all340  // instances of chokidar, regardless of usage/dependency depth)341  const envPoll = process.env.CHOKIDAR_USEPOLLING;342  if (envPoll !== undefined) {343    const envLower = envPoll.toLowerCase();344 345    if (envLower === 'false' || envLower === '0') {346      opts.usePolling = false;347    } else if (envLower === 'true' || envLower === '1') {348      opts.usePolling = true;349    } else {350      opts.usePolling = !!envLower;351    }352  }353  const envInterval = process.env.CHOKIDAR_INTERVAL;354  if (envInterval) {355    opts.interval = Number.parseInt(envInterval, 10);356  }357 358  // Editor atomic write normalization enabled by default with fs.watch359  if (undef(opts, 'atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents;360  if (opts.atomic) this._pendingUnlinks = new Map();361 362  if (undef(opts, 'followSymlinks')) opts.followSymlinks = true;363 364  if (undef(opts, 'awaitWriteFinish')) opts.awaitWriteFinish = false;365  if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {};366  const awf = opts.awaitWriteFinish;367  if (awf) {368    if (!awf.stabilityThreshold) awf.stabilityThreshold = 2000;369    if (!awf.pollInterval) awf.pollInterval = 100;370    this._pendingWrites = new Map();371  }372  if (opts.ignored) opts.ignored = arrify(opts.ignored);373 374  let readyCalls = 0;375  this._emitReady = () => {376    readyCalls++;377    if (readyCalls >= this._readyCount) {378      this._emitReady = EMPTY_FN;379      this._readyEmitted = true;380      // use process.nextTick to allow time for listener to be bound381      process.nextTick(() => this.emit(EV_READY));382    }383  };384  this._emitRaw = (...args) => this.emit(EV_RAW, ...args);385  this._readyEmitted = false;386  this.options = opts;387 388  // Initialize with proper watcher.389  if (opts.useFsEvents) {390    this._fsEventsHandler = new FsEventsHandler(this);391  } else {392    this._nodeFsHandler = new NodeFsHandler(this);393  }394 395  // You’re frozen when your heart’s not open.396  Object.freeze(opts);397}398 399// Public methods400 401/**402 * Adds paths to be watched on an existing FSWatcher instance403 * @param {Path|Array<Path>} paths_404 * @param {String=} _origAdd private; for handling non-existent paths to be watched405 * @param {Boolean=} _internal private; indicates a non-user add406 * @returns {FSWatcher} for chaining407 */408add(paths_, _origAdd, _internal) {409  const {cwd, disableGlobbing} = this.options;410  this.closed = false;411  let paths = unifyPaths(paths_);412  if (cwd) {413    paths = paths.map((path) => {414      const absPath = getAbsolutePath(path, cwd);415 416      // Check `path` instead of `absPath` because the cwd portion can't be a glob417      if (disableGlobbing || !isGlob(path)) {418        return absPath;419      }420      return normalizePath(absPath);421    });422  }423 424  // set aside negated glob strings425  paths = paths.filter((path) => {426    if (path.startsWith(BANG)) {427      this._ignoredPaths.add(path.slice(1));428      return false;429    }430 431    // if a path is being added that was previously ignored, stop ignoring it432    this._ignoredPaths.delete(path);433    this._ignoredPaths.delete(path + SLASH_GLOBSTAR);434 435    // reset the cached userIgnored anymatch fn436    // to make ignoredPaths changes effective437    this._userIgnored = undefined;438 439    return true;440  });441 442  if (this.options.useFsEvents && this._fsEventsHandler) {443    if (!this._readyCount) this._readyCount = paths.length;444    if (this.options.persistent) this._readyCount += paths.length;445    paths.forEach((path) => this._fsEventsHandler._addToFsEvents(path));446  } else {447    if (!this._readyCount) this._readyCount = 0;448    this._readyCount += paths.length;449    Promise.all(450      paths.map(async path => {451        const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, 0, 0, _origAdd);452        if (res) this._emitReady();453        return res;454      })455    ).then(results => {456      if (this.closed) return;457      results.filter(item => item).forEach(item => {458        this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));459      });460    });461  }462 463  return this;464}465 466/**467 * Close watchers or start ignoring events from specified paths.468 * @param {Path|Array<Path>} paths_ - string or array of strings, file/directory paths and/or globs469 * @returns {FSWatcher} for chaining470*/471unwatch(paths_) {472  if (this.closed) return this;473  const paths = unifyPaths(paths_);474  const {cwd} = this.options;475 476  paths.forEach((path) => {477    // convert to absolute path unless relative path already matches478    if (!sysPath.isAbsolute(path) && !this._closers.has(path)) {479      if (cwd) path = sysPath.join(cwd, path);480      path = sysPath.resolve(path);481    }482 483    this._closePath(path);484 485    this._ignoredPaths.add(path);486    if (this._watched.has(path)) {487      this._ignoredPaths.add(path + SLASH_GLOBSTAR);488    }489 490    // reset the cached userIgnored anymatch fn491    // to make ignoredPaths changes effective492    this._userIgnored = undefined;493  });494 495  return this;496}497 498/**499 * Close watchers and remove all listeners from watched paths.500 * @returns {Promise<void>}.501*/502close() {503  if (this.closed) return this._closePromise;504  this.closed = true;505 506  // Memory management.507  this.removeAllListeners();508  const closers = [];509  this._closers.forEach(closerList => closerList.forEach(closer => {510    const promise = closer();511    if (promise instanceof Promise) closers.push(promise);512  }));513  this._streams.forEach(stream => stream.destroy());514  this._userIgnored = undefined;515  this._readyCount = 0;516  this._readyEmitted = false;517  this._watched.forEach(dirent => dirent.dispose());518  ['closers', 'watched', 'streams', 'symlinkPaths', 'throttled'].forEach(key => {519    this[`_${key}`].clear();520  });521 522  this._closePromise = closers.length ? Promise.all(closers).then(() => undefined) : Promise.resolve();523  return this._closePromise;524}525 526/**527 * Expose list of watched paths528 * @returns {Object} for chaining529*/530getWatched() {531  const watchList = {};532  this._watched.forEach((entry, dir) => {533    const key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir;534    watchList[key || ONE_DOT] = entry.getChildren().sort();535  });536  return watchList;537}538 539emitWithAll(event, args) {540  this.emit(...args);541  if (event !== EV_ERROR) this.emit(EV_ALL, ...args);542}543 544// Common helpers545// --------------546 547/**548 * Normalize and emit events.549 * Calling _emit DOES NOT MEAN emit() would be called!550 * @param {EventName} event Type of event551 * @param {Path} path File or directory path552 * @param {*=} val1 arguments to be passed with event553 * @param {*=} val2554 * @param {*=} val3555 * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag556 */557async _emit(event, path, val1, val2, val3) {558  if (this.closed) return;559 560  const opts = this.options;561  if (isWindows) path = sysPath.normalize(path);562  if (opts.cwd) path = sysPath.relative(opts.cwd, path);563  /** @type Array<any> */564  const args = [event, path];565  if (val3 !== undefined) args.push(val1, val2, val3);566  else if (val2 !== undefined) args.push(val1, val2);567  else if (val1 !== undefined) args.push(val1);568 569  const awf = opts.awaitWriteFinish;570  let pw;571  if (awf && (pw = this._pendingWrites.get(path))) {572    pw.lastChange = new Date();573    return this;574  }575 576  if (opts.atomic) {577    if (event === EV_UNLINK) {578      this._pendingUnlinks.set(path, args);579      setTimeout(() => {580        this._pendingUnlinks.forEach((entry, path) => {581          this.emit(...entry);582          this.emit(EV_ALL, ...entry);583          this._pendingUnlinks.delete(path);584        });585      }, typeof opts.atomic === 'number' ? opts.atomic : 100);586      return this;587    }588    if (event === EV_ADD && this._pendingUnlinks.has(path)) {589      event = args[0] = EV_CHANGE;590      this._pendingUnlinks.delete(path);591    }592  }593 594  if (awf && (event === EV_ADD || event === EV_CHANGE) && this._readyEmitted) {595    const awfEmit = (err, stats) => {596      if (err) {597        event = args[0] = EV_ERROR;598        args[1] = err;599        this.emitWithAll(event, args);600      } else if (stats) {601        // if stats doesn't exist the file must have been deleted602        if (args.length > 2) {603          args[2] = stats;604        } else {605          args.push(stats);606        }607        this.emitWithAll(event, args);608      }609    };610 611    this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);612    return this;613  }614 615  if (event === EV_CHANGE) {616    const isThrottled = !this._throttle(EV_CHANGE, path, 50);617    if (isThrottled) return this;618  }619 620  if (opts.alwaysStat && val1 === undefined &&621    (event === EV_ADD || event === EV_ADD_DIR || event === EV_CHANGE)622  ) {623    const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path;624    let stats;625    try {626      stats = await stat(fullPath);627    } catch (err) {}628    // Suppress event when fs_stat fails, to avoid sending undefined 'stat'629    if (!stats || this.closed) return;630    args.push(stats);631  }632  this.emitWithAll(event, args);633 634  return this;635}636 637/**638 * Common handler for errors639 * @param {Error} error640 * @returns {Error|Boolean} The error if defined, otherwise the value of the FSWatcher instance's `closed` flag641 */642_handleError(error) {643  const code = error && error.code;644  if (error && code !== 'ENOENT' && code !== 'ENOTDIR' &&645    (!this.options.ignorePermissionErrors || (code !== 'EPERM' && code !== 'EACCES'))646  ) {647    this.emit(EV_ERROR, error);648  }649  return error || this.closed;650}651 652/**653 * Helper utility for throttling654 * @param {ThrottleType} actionType type being throttled655 * @param {Path} path being acted upon656 * @param {Number} timeout duration of time to suppress duplicate actions657 * @returns {Object|false} tracking object or false if action should be suppressed658 */659_throttle(actionType, path, timeout) {660  if (!this._throttled.has(actionType)) {661    this._throttled.set(actionType, new Map());662  }663 664  /** @type {Map<Path, Object>} */665  const action = this._throttled.get(actionType);666  /** @type {Object} */667  const actionPath = action.get(path);668 669  if (actionPath) {670    actionPath.count++;671    return false;672  }673 674  let timeoutObject;675  const clear = () => {676    const item = action.get(path);677    const count = item ? item.count : 0;678    action.delete(path);679    clearTimeout(timeoutObject);680    if (item) clearTimeout(item.timeoutObject);681    return count;682  };683  timeoutObject = setTimeout(clear, timeout);684  const thr = {timeoutObject, clear, count: 0};685  action.set(path, thr);686  return thr;687}688 689_incrReadyCount() {690  return this._readyCount++;691}692 693/**694 * Awaits write operation to finish.695 * Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.696 * @param {Path} path being acted upon697 * @param {Number} threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished698 * @param {EventName} event699 * @param {Function} awfEmit Callback to be called when ready for event to be emitted.700 */701_awaitWriteFinish(path, threshold, event, awfEmit) {702  let timeoutHandler;703 704  let fullPath = path;705  if (this.options.cwd && !sysPath.isAbsolute(path)) {706    fullPath = sysPath.join(this.options.cwd, path);707  }708 709  const now = new Date();710 711  const awaitWriteFinish = (prevStat) => {712    fs.stat(fullPath, (err, curStat) => {713      if (err || !this._pendingWrites.has(path)) {714        if (err && err.code !== 'ENOENT') awfEmit(err);715        return;716      }717 718      const now = Number(new Date());719 720      if (prevStat && curStat.size !== prevStat.size) {721        this._pendingWrites.get(path).lastChange = now;722      }723      const pw = this._pendingWrites.get(path);724      const df = now - pw.lastChange;725 726      if (df >= threshold) {727        this._pendingWrites.delete(path);728        awfEmit(undefined, curStat);729      } else {730        timeoutHandler = setTimeout(731          awaitWriteFinish,732          this.options.awaitWriteFinish.pollInterval,733          curStat734        );735      }736    });737  };738 739  if (!this._pendingWrites.has(path)) {740    this._pendingWrites.set(path, {741      lastChange: now,742      cancelWait: () => {743        this._pendingWrites.delete(path);744        clearTimeout(timeoutHandler);745        return event;746      }747    });748    timeoutHandler = setTimeout(749      awaitWriteFinish,750      this.options.awaitWriteFinish.pollInterval751    );752  }753}754 755_getGlobIgnored() {756  return [...this._ignoredPaths.values()];757}758 759/**760 * Determines whether user has asked to ignore this path.761 * @param {Path} path filepath or dir762 * @param {fs.Stats=} stats result of fs.stat763 * @returns {Boolean}764 */765_isIgnored(path, stats) {766  if (this.options.atomic && DOT_RE.test(path)) return true;767  if (!this._userIgnored) {768    const {cwd} = this.options;769    const ign = this.options.ignored;770 771    const ignored = ign && ign.map(normalizeIgnored(cwd));772    const paths = arrify(ignored)773      .filter((path) => typeof path === STRING_TYPE && !isGlob(path))774      .map((path) => path + SLASH_GLOBSTAR);775    const list = this._getGlobIgnored().map(normalizeIgnored(cwd)).concat(ignored, paths);776    this._userIgnored = anymatch(list, undefined, ANYMATCH_OPTS);777  }778 779  return this._userIgnored([path, stats]);780}781 782_isntIgnored(path, stat) {783  return !this._isIgnored(path, stat);784}785 786/**787 * Provides a set of common helpers and properties relating to symlink and glob handling.788 * @param {Path} path file, directory, or glob pattern being watched789 * @param {Number=} depth at any depth > 0, this isn't a glob790 * @returns {WatchHelper} object containing helpers for this path791 */792_getWatchHelpers(path, depth) {793  const watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path);794  const follow = this.options.followSymlinks;795 796  return new WatchHelper(path, watchPath, follow, this);797}798 799// Directory helpers800// -----------------801 802/**803 * Provides directory tracking objects804 * @param {String} directory path of the directory805 * @returns {DirEntry} the directory's tracking object806 */807_getWatchedDir(directory) {808  if (!this._boundRemove) this._boundRemove = this._remove.bind(this);809  const dir = sysPath.resolve(directory);810  if (!this._watched.has(dir)) this._watched.set(dir, new DirEntry(dir, this._boundRemove));811  return this._watched.get(dir);812}813 814// File helpers815// ------------816 817/**818 * Check for read permissions.819 * Based on this answer on SO: https://stackoverflow.com/a/11781404/1358405820 * @param {fs.Stats} stats - object, result of fs_stat821 * @returns {Boolean} indicates whether the file can be read822*/823_hasReadPermissions(stats) {824  if (this.options.ignorePermissionErrors) return true;825 826  // stats.mode may be bigint827  const md = stats && Number.parseInt(stats.mode, 10);828  const st = md & 0o777;829  const it = Number.parseInt(st.toString(8)[0], 10);830  return Boolean(4 & it);831}832 833/**834 * Handles emitting unlink events for835 * files and directories, and via recursion, for836 * files and directories within directories that are unlinked837 * @param {String} directory within which the following item is located838 * @param {String} item      base path of item/directory839 * @returns {void}840*/841_remove(directory, item, isDirectory) {842  // if what is being deleted is a directory, get that directory's paths843  // for recursive deleting and cleaning of watched object844  // if it is not a directory, nestedDirectoryChildren will be empty array845  const path = sysPath.join(directory, item);846  const fullPath = sysPath.resolve(path);847  isDirectory = isDirectory != null848    ? isDirectory849    : this._watched.has(path) || this._watched.has(fullPath);850 851  // prevent duplicate handling in case of arriving here nearly simultaneously852  // via multiple paths (such as _handleFile and _handleDir)853  if (!this._throttle('remove', path, 100)) return;854 855  // if the only watched file is removed, watch for its return856  if (!isDirectory && !this.options.useFsEvents && this._watched.size === 1) {857    this.add(directory, item, true);858  }859 860  // This will create a new entry in the watched object in either case861  // so we got to do the directory check beforehand862  const wp = this._getWatchedDir(path);863  const nestedDirectoryChildren = wp.getChildren();864 865  // Recursively remove children directories / files.866  nestedDirectoryChildren.forEach(nested => this._remove(path, nested));867 868  // Check if item was on the watched list and remove it869  const parent = this._getWatchedDir(directory);870  const wasTracked = parent.has(item);871  parent.remove(item);872 873  // Fixes issue #1042 -> Relative paths were detected and added as symlinks874  // (https://github.com/paulmillr/chokidar/blob/e1753ddbc9571bdc33b4a4af172d52cb6e611c10/lib/nodefs-handler.js#L612),875  // but never removed from the map in case the path was deleted.876  // This leads to an incorrect state if the path was recreated:877  // https://github.com/paulmillr/chokidar/blob/e1753ddbc9571bdc33b4a4af172d52cb6e611c10/lib/nodefs-handler.js#L553878  if (this._symlinkPaths.has(fullPath)) {879    this._symlinkPaths.delete(fullPath);880  }881 882  // If we wait for this file to be fully written, cancel the wait.883  let relPath = path;884  if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path);885  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {886    const event = this._pendingWrites.get(relPath).cancelWait();887    if (event === EV_ADD) return;888  }889 890  // The Entry will either be a directory that just got removed891  // or a bogus entry to a file, in either case we have to remove it892  this._watched.delete(path);893  this._watched.delete(fullPath);894  const eventName = isDirectory ? EV_UNLINK_DIR : EV_UNLINK;895  if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path);896 897  // Avoid conflicts if we later create another file with the same name898  if (!this.options.useFsEvents) {899    this._closePath(path);900  }901}902 903/**904 * Closes all watchers for a path905 * @param {Path} path906 */907_closePath(path) {908  this._closeFile(path)909  const dir = sysPath.dirname(path);910  this._getWatchedDir(dir).remove(sysPath.basename(path));911}912 913/**914 * Closes only file-specific watchers915 * @param {Path} path916 */917_closeFile(path) {918  const closers = this._closers.get(path);919  if (!closers) return;920  closers.forEach(closer => closer());921  this._closers.delete(path);922}923 924/**925 *926 * @param {Path} path927 * @param {Function} closer928 */929_addPathCloser(path, closer) {930  if (!closer) return;931  let list = this._closers.get(path);932  if (!list) {933    list = [];934    this._closers.set(path, list);935  }936  list.push(closer);937}938 939_readdirp(root, opts) {940  if (this.closed) return;941  const options = {type: EV_ALL, alwaysStat: true, lstat: true, ...opts};942  let stream = readdirp(root, options);943  this._streams.add(stream);944  stream.once(STR_CLOSE, () => {945    stream = undefined;946  });947  stream.once(STR_END, () => {948    if (stream) {949      this._streams.delete(stream);950      stream = undefined;951    }952  });953  return stream;954}955 956}957 958// Export FSWatcher class959exports.FSWatcher = FSWatcher;960 961/**962 * Instantiates watcher with paths to be tracked.963 * @param {String|Array<String>} paths file/directory paths and/or globs964 * @param {Object=} options chokidar opts965 * @returns an instance of FSWatcher for chaining.966 */967const watch = (paths, options) => {968  const watcher = new FSWatcher(options);969  watcher.add(paths);970  return watcher;971};972 973exports.watch = watch;974