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.
		
		
		
		
			
				
					381 lines
				
				12 KiB
			
		
		
			
		
	
	
					381 lines
				
				12 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const Readable = require('stream').Readable;
							 | 
						||
| 
								 | 
							
								const EventEmitter = require('events').EventEmitter;
							 | 
						||
| 
								 | 
							
								const path = require('path');
							 | 
						||
| 
								 | 
							
								const normalizeOptions = require('./normalize-options');
							 | 
						||
| 
								 | 
							
								const stat = require('./stat');
							 | 
						||
| 
								 | 
							
								const call = require('./call');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Asynchronously reads the contents of a directory and streams the results
							 | 
						||
| 
								 | 
							
								 * via a {@link stream.Readable}.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								class DirectoryReader {
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * @param {string} dir - The absolute or relative directory path to read
							 | 
						||
| 
								 | 
							
								   * @param {object} [options] - User-specified options, if any (see {@link normalizeOptions})
							 | 
						||
| 
								 | 
							
								   * @param {object} internalOptions - Internal options that aren't part of the public API
							 | 
						||
| 
								 | 
							
								   * @class
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  constructor (dir, options, internalOptions) {
							 | 
						||
| 
								 | 
							
								    this.options = options = normalizeOptions(options, internalOptions);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Indicates whether we should keep reading
							 | 
						||
| 
								 | 
							
								    // This is set false if stream.Readable.push() returns false.
							 | 
						||
| 
								 | 
							
								    this.shouldRead = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // The directories to read
							 | 
						||
| 
								 | 
							
								    // (initialized with the top-level directory)
							 | 
						||
| 
								 | 
							
								    this.queue = [{
							 | 
						||
| 
								 | 
							
								      path: dir,
							 | 
						||
| 
								 | 
							
								      basePath: options.basePath,
							 | 
						||
| 
								 | 
							
								      posixBasePath: options.posixBasePath,
							 | 
						||
| 
								 | 
							
								      depth: 0
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // The number of directories that are currently being processed
							 | 
						||
| 
								 | 
							
								    this.pending = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // The data that has been read, but not yet emitted
							 | 
						||
| 
								 | 
							
								    this.buffer = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.stream = new Readable({ objectMode: true });
							 | 
						||
| 
								 | 
							
								    this.stream._read = () => {
							 | 
						||
| 
								 | 
							
								      // Start (or resume) reading
							 | 
						||
| 
								 | 
							
								      this.shouldRead = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // If we have data in the buffer, then send the next chunk
							 | 
						||
| 
								 | 
							
								      if (this.buffer.length > 0) {
							 | 
						||
| 
								 | 
							
								        this.pushFromBuffer();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // If we have directories queued, then start processing the next one
							 | 
						||
| 
								 | 
							
								      if (this.queue.length > 0) {
							 | 
						||
| 
								 | 
							
								        if (this.options.facade.sync) {
							 | 
						||
| 
								 | 
							
								          while (this.queue.length > 0) {
							 | 
						||
| 
								 | 
							
								            this.readNextDirectory();
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								          this.readNextDirectory();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      this.checkForEOF();
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Reads the next directory in the queue
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  readNextDirectory () {
							 | 
						||
| 
								 | 
							
								    let facade = this.options.facade;
							 | 
						||
| 
								 | 
							
								    let dir = this.queue.shift();
							 | 
						||
| 
								 | 
							
								    this.pending++;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Read the directory listing
							 | 
						||
| 
								 | 
							
								    call.safe(facade.fs.readdir, dir.path, (err, items) => {
							 | 
						||
| 
								 | 
							
								      if (err) {
							 | 
						||
| 
								 | 
							
								        // fs.readdir threw an error
							 | 
						||
| 
								 | 
							
								        this.emit('error', err);
							 | 
						||
| 
								 | 
							
								        return this.finishedReadingDirectory();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        // Process each item in the directory (simultaneously, if async)
							 | 
						||
| 
								 | 
							
								        facade.forEach(
							 | 
						||
| 
								 | 
							
								          items,
							 | 
						||
| 
								 | 
							
								          this.processItem.bind(this, dir),
							 | 
						||
| 
								 | 
							
								          this.finishedReadingDirectory.bind(this, dir)
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      catch (err2) {
							 | 
						||
| 
								 | 
							
								        // facade.forEach threw an error
							 | 
						||
| 
								 | 
							
								        // (probably because fs.readdir returned an invalid result)
							 | 
						||
| 
								 | 
							
								        this.emit('error', err2);
							 | 
						||
| 
								 | 
							
								        this.finishedReadingDirectory();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * This method is called after all items in a directory have been processed.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * NOTE: This does not necessarily mean that the reader is finished, since there may still
							 | 
						||
| 
								 | 
							
								   * be other directories queued or pending.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  finishedReadingDirectory () {
							 | 
						||
| 
								 | 
							
								    this.pending--;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (this.shouldRead) {
							 | 
						||
| 
								 | 
							
								      // If we have directories queued, then start processing the next one
							 | 
						||
| 
								 | 
							
								      if (this.queue.length > 0 && this.options.facade.async) {
							 | 
						||
| 
								 | 
							
								        this.readNextDirectory();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      this.checkForEOF();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Determines whether the reader has finished processing all items in all directories.
							 | 
						||
| 
								 | 
							
								   * If so, then the "end" event is fired (via {@Readable#push})
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  checkForEOF () {
							 | 
						||
| 
								 | 
							
								    if (this.buffer.length === 0 &&   // The stuff we've already read
							 | 
						||
| 
								 | 
							
								    this.pending === 0 &&             // The stuff we're currently reading
							 | 
						||
| 
								 | 
							
								    this.queue.length === 0) {        // The stuff we haven't read yet
							 | 
						||
| 
								 | 
							
								      // There's no more stuff!
							 | 
						||
| 
								 | 
							
								      this.stream.push(null);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Processes a single item in a directory.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * If the item is a directory, and `option.deep` is enabled, then the item will be added
							 | 
						||
| 
								 | 
							
								   * to the directory queue.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * If the item meets the filter criteria, then it will be emitted to the reader's stream.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {object} dir - A directory object from the queue
							 | 
						||
| 
								 | 
							
								   * @param {string} item - The name of the item (name only, no path)
							 | 
						||
| 
								 | 
							
								   * @param {function} done - A callback function that is called after the item has been processed
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  processItem (dir, item, done) {
							 | 
						||
| 
								 | 
							
								    let stream = this.stream;
							 | 
						||
| 
								 | 
							
								    let options = this.options;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let itemPath = dir.basePath + item;
							 | 
						||
| 
								 | 
							
								    let posixPath = dir.posixBasePath + item;
							 | 
						||
| 
								 | 
							
								    let fullPath = path.join(dir.path, item);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // If `options.deep` is a number, and we've already recursed to the max depth,
							 | 
						||
| 
								 | 
							
								    // then there's no need to check fs.Stats to know if it's a directory.
							 | 
						||
| 
								 | 
							
								    // If `options.deep` is a function, then we'll need fs.Stats
							 | 
						||
| 
								 | 
							
								    let maxDepthReached = dir.depth >= options.recurseDepth;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Do we need to call `fs.stat`?
							 | 
						||
| 
								 | 
							
								    let needStats =
							 | 
						||
| 
								 | 
							
								      !maxDepthReached ||                                 // we need the fs.Stats to know if it's a directory
							 | 
						||
| 
								 | 
							
								      options.stats ||                                    // the user wants fs.Stats objects returned
							 | 
						||
| 
								 | 
							
								      options.recurseFn ||                                // we need fs.Stats for the recurse function
							 | 
						||
| 
								 | 
							
								      options.filterFn ||                                 // we need fs.Stats for the filter function
							 | 
						||
| 
								 | 
							
								      EventEmitter.listenerCount(stream, 'file') ||       // we need the fs.Stats to know if it's a file
							 | 
						||
| 
								 | 
							
								      EventEmitter.listenerCount(stream, 'directory') ||  // we need the fs.Stats to know if it's a directory
							 | 
						||
| 
								 | 
							
								      EventEmitter.listenerCount(stream, 'symlink');      // we need the fs.Stats to know if it's a symlink
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // If we don't need stats, then exit early
							 | 
						||
| 
								 | 
							
								    if (!needStats) {
							 | 
						||
| 
								 | 
							
								      if (this.filter(itemPath, posixPath)) {
							 | 
						||
| 
								 | 
							
								        this.pushOrBuffer({ data: itemPath });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return done();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Get the fs.Stats object for this path
							 | 
						||
| 
								 | 
							
								    stat(options.facade.fs, fullPath, (err, stats) => {
							 | 
						||
| 
								 | 
							
								      if (err) {
							 | 
						||
| 
								 | 
							
								        // fs.stat threw an error
							 | 
						||
| 
								 | 
							
								        this.emit('error', err);
							 | 
						||
| 
								 | 
							
								        return done();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        // Add the item's path to the fs.Stats object
							 | 
						||
| 
								 | 
							
								        // The base of this path, and its separators are determined by the options
							 | 
						||
| 
								 | 
							
								        // (i.e. options.basePath and options.sep)
							 | 
						||
| 
								 | 
							
								        stats.path = itemPath;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Add depth of the path to the fs.Stats object for use this in the filter function
							 | 
						||
| 
								 | 
							
								        stats.depth = dir.depth;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (this.shouldRecurse(stats, posixPath, maxDepthReached)) {
							 | 
						||
| 
								 | 
							
								          // Add this subdirectory to the queue
							 | 
						||
| 
								 | 
							
								          this.queue.push({
							 | 
						||
| 
								 | 
							
								            path: fullPath,
							 | 
						||
| 
								 | 
							
								            basePath: itemPath + options.sep,
							 | 
						||
| 
								 | 
							
								            posixBasePath: posixPath + '/',
							 | 
						||
| 
								 | 
							
								            depth: dir.depth + 1,
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Determine whether this item matches the filter criteria
							 | 
						||
| 
								 | 
							
								        if (this.filter(stats, posixPath)) {
							 | 
						||
| 
								 | 
							
								          this.pushOrBuffer({
							 | 
						||
| 
								 | 
							
								            data: options.stats ? stats : itemPath,
							 | 
						||
| 
								 | 
							
								            file: stats.isFile(),
							 | 
						||
| 
								 | 
							
								            directory: stats.isDirectory(),
							 | 
						||
| 
								 | 
							
								            symlink: stats.isSymbolicLink(),
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        done();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      catch (err2) {
							 | 
						||
| 
								 | 
							
								        // An error occurred while processing the item
							 | 
						||
| 
								 | 
							
								        // (probably during a user-specified function, such as options.deep, options.filter, etc.)
							 | 
						||
| 
								 | 
							
								        this.emit('error', err2);
							 | 
						||
| 
								 | 
							
								        done();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Pushes the given chunk of data to the stream, or adds it to the buffer,
							 | 
						||
| 
								 | 
							
								   * depending on the state of the stream.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {object} chunk
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  pushOrBuffer (chunk) {
							 | 
						||
| 
								 | 
							
								    // Add the chunk to the buffer
							 | 
						||
| 
								 | 
							
								    this.buffer.push(chunk);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // If we're still reading, then immediately emit the next chunk in the buffer
							 | 
						||
| 
								 | 
							
								    // (which may or may not be the chunk that we just added)
							 | 
						||
| 
								 | 
							
								    if (this.shouldRead) {
							 | 
						||
| 
								 | 
							
								      this.pushFromBuffer();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Immediately pushes the next chunk in the buffer to the reader's stream.
							 | 
						||
| 
								 | 
							
								   * The "data" event will always be fired (via {@link Readable#push}).
							 | 
						||
| 
								 | 
							
								   * In addition, the "file", "directory", and/or "symlink" events may be fired,
							 | 
						||
| 
								 | 
							
								   * depending on the type of properties of the chunk.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  pushFromBuffer () {
							 | 
						||
| 
								 | 
							
								    let stream = this.stream;
							 | 
						||
| 
								 | 
							
								    let chunk = this.buffer.shift();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Stream the data
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      this.shouldRead = stream.push(chunk.data);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    catch (err) {
							 | 
						||
| 
								 | 
							
								      this.emit('error', err);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Also emit specific events, based on the type of chunk
							 | 
						||
| 
								 | 
							
								    chunk.file && this.emit('file', chunk.data);
							 | 
						||
| 
								 | 
							
								    chunk.symlink && this.emit('symlink', chunk.data);
							 | 
						||
| 
								 | 
							
								    chunk.directory && this.emit('directory', chunk.data);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Determines whether the given directory meets the user-specified recursion criteria.
							 | 
						||
| 
								 | 
							
								   * If the user didn't specify recursion criteria, then this function will default to true.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {fs.Stats} stats - The directory's {@link fs.Stats} object
							 | 
						||
| 
								 | 
							
								   * @param {string} posixPath - The item's POSIX path (used for glob matching)
							 | 
						||
| 
								 | 
							
								   * @param {boolean} maxDepthReached - Whether we've already crawled the user-specified depth
							 | 
						||
| 
								 | 
							
								   * @returns {boolean}
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  shouldRecurse (stats, posixPath, maxDepthReached) {
							 | 
						||
| 
								 | 
							
								    let options = this.options;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (maxDepthReached) {
							 | 
						||
| 
								 | 
							
								      // We've already crawled to the maximum depth. So no more recursion.
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (!stats.isDirectory()) {
							 | 
						||
| 
								 | 
							
								      // It's not a directory. So don't try to crawl it.
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (options.recurseGlob) {
							 | 
						||
| 
								 | 
							
								      // Glob patterns are always tested against the POSIX path, even on Windows
							 | 
						||
| 
								 | 
							
								      // https://github.com/isaacs/node-glob#windows
							 | 
						||
| 
								 | 
							
								      return options.recurseGlob.test(posixPath);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (options.recurseRegExp) {
							 | 
						||
| 
								 | 
							
								      // Regular expressions are tested against the normal path
							 | 
						||
| 
								 | 
							
								      // (based on the OS or options.sep)
							 | 
						||
| 
								 | 
							
								      return options.recurseRegExp.test(stats.path);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (options.recurseFn) {
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        // Run the user-specified recursion criteria
							 | 
						||
| 
								 | 
							
								        return options.recurseFn.call(null, stats);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      catch (err) {
							 | 
						||
| 
								 | 
							
								        // An error occurred in the user's code.
							 | 
						||
| 
								 | 
							
								        // In Sync and Async modes, this will return an error.
							 | 
						||
| 
								 | 
							
								        // In Streaming mode, we emit an "error" event, but continue processing
							 | 
						||
| 
								 | 
							
								        this.emit('error', err);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								      // No recursion function was specified, and we're within the maximum depth.
							 | 
						||
| 
								 | 
							
								      // So crawl this directory.
							 | 
						||
| 
								 | 
							
								      return true;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Determines whether the given item meets the user-specified filter criteria.
							 | 
						||
| 
								 | 
							
								   * If the user didn't specify a filter, then this function will always return true.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {string|fs.Stats} value - Either the item's path, or the item's {@link fs.Stats} object
							 | 
						||
| 
								 | 
							
								   * @param {string} posixPath - The item's POSIX path (used for glob matching)
							 | 
						||
| 
								 | 
							
								   * @returns {boolean}
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  filter (value, posixPath) {
							 | 
						||
| 
								 | 
							
								    let options = this.options;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (options.filterGlob) {
							 | 
						||
| 
								 | 
							
								      // Glob patterns are always tested against the POSIX path, even on Windows
							 | 
						||
| 
								 | 
							
								      // https://github.com/isaacs/node-glob#windows
							 | 
						||
| 
								 | 
							
								      return options.filterGlob.test(posixPath);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (options.filterRegExp) {
							 | 
						||
| 
								 | 
							
								      // Regular expressions are tested against the normal path
							 | 
						||
| 
								 | 
							
								      // (based on the OS or options.sep)
							 | 
						||
| 
								 | 
							
								      return options.filterRegExp.test(value.path || value);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (options.filterFn) {
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        // Run the user-specified filter function
							 | 
						||
| 
								 | 
							
								        return options.filterFn.call(null, value);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      catch (err) {
							 | 
						||
| 
								 | 
							
								        // An error occurred in the user's code.
							 | 
						||
| 
								 | 
							
								        // In Sync and Async modes, this will return an error.
							 | 
						||
| 
								 | 
							
								        // In Streaming mode, we emit an "error" event, but continue processing
							 | 
						||
| 
								 | 
							
								        this.emit('error', err);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								      // No filter was specified, so match everything
							 | 
						||
| 
								 | 
							
								      return true;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Emits an event.  If one of the event listeners throws an error,
							 | 
						||
| 
								 | 
							
								   * then an "error" event is emitted.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {string} eventName
							 | 
						||
| 
								 | 
							
								   * @param {*} data
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  emit (eventName, data) {
							 | 
						||
| 
								 | 
							
								    let stream = this.stream;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      stream.emit(eventName, data);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    catch (err) {
							 | 
						||
| 
								 | 
							
								      if (eventName === 'error') {
							 | 
						||
| 
								 | 
							
								        // Don't recursively emit "error" events.
							 | 
						||
| 
								 | 
							
								        // If the first one fails, then just throw
							 | 
						||
| 
								 | 
							
								        throw err;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      else {
							 | 
						||
| 
								 | 
							
								        stream.emit('error', err);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = DirectoryReader;
							 |