aboutsummaryrefslogtreecommitdiff
path: root/node_modules/@mrmlnc/readdir-enhanced/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@mrmlnc/readdir-enhanced/lib')
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/async/for-each.js29
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/async/index.js48
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/call.js54
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/directory-reader.js380
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/index.js85
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/normalize-options.js177
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/stat.js74
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/stream/index.js25
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/sync/for-each.js22
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/sync/fs.js64
-rw-r--r--node_modules/@mrmlnc/readdir-enhanced/lib/sync/index.js34
11 files changed, 992 insertions, 0 deletions
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/async/for-each.js b/node_modules/@mrmlnc/readdir-enhanced/lib/async/for-each.js
new file mode 100644
index 0000000..1ac9b2f
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/async/for-each.js
@@ -0,0 +1,29 @@
+'use strict';
+
+module.exports = asyncForEach;
+
+/**
+ * Simultaneously processes all items in the given array.
+ *
+ * @param {array} array - The array to iterate over
+ * @param {function} iterator - The function to call for each item in the array
+ * @param {function} done - The function to call when all iterators have completed
+ */
+function asyncForEach (array, iterator, done) {
+ if (array.length === 0) {
+ // NOTE: Normally a bad idea to mix sync and async, but it's safe here because
+ // of the way that this method is currently used by DirectoryReader.
+ done();
+ return;
+ }
+
+ // Simultaneously process all items in the array.
+ let pending = array.length;
+ array.forEach(item => {
+ iterator(item, () => {
+ if (--pending === 0) {
+ done();
+ }
+ });
+ });
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/async/index.js b/node_modules/@mrmlnc/readdir-enhanced/lib/async/index.js
new file mode 100644
index 0000000..677e0b6
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/async/index.js
@@ -0,0 +1,48 @@
+'use strict';
+
+module.exports = readdirAsync;
+
+const maybe = require('call-me-maybe');
+const DirectoryReader = require('../directory-reader');
+
+let asyncFacade = {
+ fs: require('fs'),
+ forEach: require('./for-each'),
+ async: true
+};
+
+/**
+ * Returns the buffered output from an asynchronous {@link DirectoryReader},
+ * via an error-first callback or a {@link Promise}.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @param {function} [callback]
+ * @param {object} internalOptions
+ */
+function readdirAsync (dir, options, callback, internalOptions) {
+ if (typeof options === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ return maybe(callback, new Promise(((resolve, reject) => {
+ let results = [];
+
+ internalOptions.facade = asyncFacade;
+
+ let reader = new DirectoryReader(dir, options, internalOptions);
+ let stream = reader.stream;
+
+ stream.on('error', err => {
+ reject(err);
+ stream.pause();
+ });
+ stream.on('data', result => {
+ results.push(result);
+ });
+ stream.on('end', () => {
+ resolve(results);
+ });
+ })));
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/call.js b/node_modules/@mrmlnc/readdir-enhanced/lib/call.js
new file mode 100644
index 0000000..07e3d84
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/call.js
@@ -0,0 +1,54 @@
+'use strict';
+
+let call = module.exports = {
+ safe: safeCall,
+ once: callOnce,
+};
+
+/**
+ * Calls a function with the given arguments, and ensures that the error-first callback is _always_
+ * invoked exactly once, even if the function throws an error.
+ *
+ * @param {function} fn - The function to invoke
+ * @param {...*} args - The arguments to pass to the function. The final argument must be a callback function.
+ */
+function safeCall (fn, args) {
+ // Get the function arguments as an array
+ args = Array.prototype.slice.call(arguments, 1);
+
+ // Replace the callback function with a wrapper that ensures it will only be called once
+ let callback = call.once(args.pop());
+ args.push(callback);
+
+ try {
+ fn.apply(null, args);
+ }
+ catch (err) {
+ callback(err);
+ }
+}
+
+/**
+ * Returns a wrapper function that ensures the given callback function is only called once.
+ * Subsequent calls are ignored, unless the first argument is an Error, in which case the
+ * error is thrown.
+ *
+ * @param {function} fn - The function that should only be called once
+ * @returns {function}
+ */
+function callOnce (fn) {
+ let fulfilled = false;
+
+ return function onceWrapper (err) {
+ if (!fulfilled) {
+ fulfilled = true;
+ return fn.apply(this, arguments);
+ }
+ else if (err) {
+ // The callback has already been called, but now an error has occurred
+ // (most likely inside the callback function). So re-throw the error,
+ // so it gets handled further up the call stack
+ throw err;
+ }
+ };
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/directory-reader.js b/node_modules/@mrmlnc/readdir-enhanced/lib/directory-reader.js
new file mode 100644
index 0000000..569d793
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/directory-reader.js
@@ -0,0 +1,380 @@
+'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;
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/index.js b/node_modules/@mrmlnc/readdir-enhanced/lib/index.js
new file mode 100644
index 0000000..f77d2c6
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/index.js
@@ -0,0 +1,85 @@
+'use strict';
+
+const readdirSync = require('./sync');
+const readdirAsync = require('./async');
+const readdirStream = require('./stream');
+
+module.exports = exports = readdirAsyncPath;
+exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath;
+exports.readdirAsyncStat = exports.async.stat = readdirAsyncStat;
+exports.readdirStream = exports.stream = readdirStreamPath;
+exports.readdirStreamStat = exports.stream.stat = readdirStreamStat;
+exports.readdirSync = exports.sync = readdirSyncPath;
+exports.readdirSyncStat = exports.sync.stat = readdirSyncStat;
+
+/**
+ * Synchronous readdir that returns an array of string paths.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @returns {string[]}
+ */
+function readdirSyncPath (dir, options) {
+ return readdirSync(dir, options, {});
+}
+
+/**
+ * Synchronous readdir that returns results as an array of {@link fs.Stats} objects
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @returns {fs.Stats[]}
+ */
+function readdirSyncStat (dir, options) {
+ return readdirSync(dir, options, { stats: true });
+}
+
+/**
+ * Aynchronous readdir (accepts an error-first callback or returns a {@link Promise}).
+ * Results are an array of path strings.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @param {function} [callback]
+ * @returns {Promise<string[]>}
+ */
+function readdirAsyncPath (dir, options, callback) {
+ return readdirAsync(dir, options, callback, {});
+}
+
+/**
+ * Aynchronous readdir (accepts an error-first callback or returns a {@link Promise}).
+ * Results are an array of {@link fs.Stats} objects.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @param {function} [callback]
+ * @returns {Promise<fs.Stats[]>}
+ */
+function readdirAsyncStat (dir, options, callback) {
+ return readdirAsync(dir, options, callback, { stats: true });
+}
+
+/**
+ * Aynchronous readdir that returns a {@link stream.Readable} (which is also an {@link EventEmitter}).
+ * All stream data events ("data", "file", "directory", "symlink") are passed a path string.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @returns {stream.Readable}
+ */
+function readdirStreamPath (dir, options) {
+ return readdirStream(dir, options, {});
+}
+
+/**
+ * Aynchronous readdir that returns a {@link stream.Readable} (which is also an {@link EventEmitter})
+ * All stream data events ("data", "file", "directory", "symlink") are passed an {@link fs.Stats} object.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @returns {stream.Readable}
+ */
+function readdirStreamStat (dir, options) {
+ return readdirStream(dir, options, { stats: true });
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/normalize-options.js b/node_modules/@mrmlnc/readdir-enhanced/lib/normalize-options.js
new file mode 100644
index 0000000..66f1158
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/normalize-options.js
@@ -0,0 +1,177 @@
+'use strict';
+
+const path = require('path');
+const globToRegExp = require('glob-to-regexp');
+
+module.exports = normalizeOptions;
+
+let isWindows = /^win/.test(process.platform);
+
+/**
+ * @typedef {Object} FSFacade
+ * @property {fs.readdir} readdir
+ * @property {fs.stat} stat
+ * @property {fs.lstat} lstat
+ */
+
+/**
+ * Validates and normalizes the options argument
+ *
+ * @param {object} [options] - User-specified options, if any
+ * @param {object} internalOptions - Internal options that aren't part of the public API
+ *
+ * @param {number|boolean|function} [options.deep]
+ * The number of directories to recursively traverse. Any falsy value or negative number will
+ * default to zero, so only the top-level contents will be returned. Set to `true` or `Infinity`
+ * to traverse all subdirectories. Or provide a function that accepts a {@link fs.Stats} object
+ * and returns a truthy value if the directory's contents should be crawled.
+ *
+ * @param {function|string|RegExp} [options.filter]
+ * A function that accepts a {@link fs.Stats} object and returns a truthy value if the data should
+ * be returned. Or a RegExp or glob string pattern, to filter by file name.
+ *
+ * @param {string} [options.sep]
+ * The path separator to use. By default, the OS-specific separator will be used, but this can be
+ * set to a specific value to ensure consistency across platforms.
+ *
+ * @param {string} [options.basePath]
+ * The base path to prepend to each result. If empty, then all results will be relative to `dir`.
+ *
+ * @param {FSFacade} [options.fs]
+ * Synchronous or asynchronous facades for Node.js File System module
+ *
+ * @param {object} [internalOptions.facade]
+ * Synchronous or asynchronous facades for various methods, including for the Node.js File System module
+ *
+ * @param {boolean} [internalOptions.emit]
+ * Indicates whether the reader should emit "file", "directory", and "symlink" events
+ *
+ * @param {boolean} [internalOptions.stats]
+ * Indicates whether the reader should emit {@link fs.Stats} objects instead of path strings
+ *
+ * @returns {object}
+ */
+function normalizeOptions (options, internalOptions) {
+ if (options === null || options === undefined) {
+ options = {};
+ }
+ else if (typeof options !== 'object') {
+ throw new TypeError('options must be an object');
+ }
+
+ let recurseDepth, recurseFn, recurseRegExp, recurseGlob, deep = options.deep;
+ if (deep === null || deep === undefined) {
+ recurseDepth = 0;
+ }
+ else if (typeof deep === 'boolean') {
+ recurseDepth = deep ? Infinity : 0;
+ }
+ else if (typeof deep === 'number') {
+ if (deep < 0 || isNaN(deep)) {
+ throw new Error('options.deep must be a positive number');
+ }
+ else if (Math.floor(deep) !== deep) {
+ throw new Error('options.deep must be an integer');
+ }
+ else {
+ recurseDepth = deep;
+ }
+ }
+ else if (typeof deep === 'function') {
+ recurseDepth = Infinity;
+ recurseFn = deep;
+ }
+ else if (deep instanceof RegExp) {
+ recurseDepth = Infinity;
+ recurseRegExp = deep;
+ }
+ else if (typeof deep === 'string' && deep.length > 0) {
+ recurseDepth = Infinity;
+ recurseGlob = globToRegExp(deep, { extended: true, globstar: true });
+ }
+ else {
+ throw new TypeError('options.deep must be a boolean, number, function, regular expression, or glob pattern');
+ }
+
+ let filterFn, filterRegExp, filterGlob, filter = options.filter;
+ if (filter !== null && filter !== undefined) {
+ if (typeof filter === 'function') {
+ filterFn = filter;
+ }
+ else if (filter instanceof RegExp) {
+ filterRegExp = filter;
+ }
+ else if (typeof filter === 'string' && filter.length > 0) {
+ filterGlob = globToRegExp(filter, { extended: true, globstar: true });
+ }
+ else {
+ throw new TypeError('options.filter must be a function, regular expression, or glob pattern');
+ }
+ }
+
+ let sep = options.sep;
+ if (sep === null || sep === undefined) {
+ sep = path.sep;
+ }
+ else if (typeof sep !== 'string') {
+ throw new TypeError('options.sep must be a string');
+ }
+
+ let basePath = options.basePath;
+ if (basePath === null || basePath === undefined) {
+ basePath = '';
+ }
+ else if (typeof basePath === 'string') {
+ // Append a path separator to the basePath, if necessary
+ if (basePath && basePath.substr(-1) !== sep) {
+ basePath += sep;
+ }
+ }
+ else {
+ throw new TypeError('options.basePath must be a string');
+ }
+
+ // Convert the basePath to POSIX (forward slashes)
+ // so that glob pattern matching works consistently, even on Windows
+ let posixBasePath = basePath;
+ if (posixBasePath && sep !== '/') {
+ posixBasePath = posixBasePath.replace(new RegExp('\\' + sep, 'g'), '/');
+
+ /* istanbul ignore if */
+ if (isWindows) {
+ // Convert Windows root paths (C:\) and UNCs (\\) to POSIX root paths
+ posixBasePath = posixBasePath.replace(/^([a-zA-Z]\:\/|\/\/)/, '/');
+ }
+ }
+
+ // Determine which facade methods to use
+ let facade;
+ if (options.fs === null || options.fs === undefined) {
+ // The user didn't provide their own facades, so use our internal ones
+ facade = internalOptions.facade;
+ }
+ else if (typeof options.fs === 'object') {
+ // Merge the internal facade methods with the user-provided `fs` facades
+ facade = Object.assign({}, internalOptions.facade);
+ facade.fs = Object.assign({}, internalOptions.facade.fs, options.fs);
+ }
+ else {
+ throw new TypeError('options.fs must be an object');
+ }
+
+ return {
+ recurseDepth,
+ recurseFn,
+ recurseRegExp,
+ recurseGlob,
+ filterFn,
+ filterRegExp,
+ filterGlob,
+ sep,
+ basePath,
+ posixBasePath,
+ facade,
+ emit: !!internalOptions.emit,
+ stats: !!internalOptions.stats,
+ };
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/stat.js b/node_modules/@mrmlnc/readdir-enhanced/lib/stat.js
new file mode 100644
index 0000000..e338693
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/stat.js
@@ -0,0 +1,74 @@
+'use strict';
+
+const call = require('./call');
+
+module.exports = stat;
+
+/**
+ * Retrieves the {@link fs.Stats} for the given path. If the path is a symbolic link,
+ * then the Stats of the symlink's target are returned instead. If the symlink is broken,
+ * then the Stats of the symlink itself are returned.
+ *
+ * @param {object} fs - Synchronous or Asynchronouse facade for the "fs" module
+ * @param {string} path - The path to return stats for
+ * @param {function} callback
+ */
+function stat (fs, path, callback) {
+ let isSymLink = false;
+
+ call.safe(fs.lstat, path, (err, lstats) => {
+ if (err) {
+ // fs.lstat threw an eror
+ return callback(err);
+ }
+
+ try {
+ isSymLink = lstats.isSymbolicLink();
+ }
+ catch (err2) {
+ // lstats.isSymbolicLink() threw an error
+ // (probably because fs.lstat returned an invalid result)
+ return callback(err2);
+ }
+
+ if (isSymLink) {
+ // Try to resolve the symlink
+ symlinkStat(fs, path, lstats, callback);
+ }
+ else {
+ // It's not a symlink, so return the stats as-is
+ callback(null, lstats);
+ }
+ });
+}
+
+/**
+ * Retrieves the {@link fs.Stats} for the target of the given symlink.
+ * If the symlink is broken, then the Stats of the symlink itself are returned.
+ *
+ * @param {object} fs - Synchronous or Asynchronouse facade for the "fs" module
+ * @param {string} path - The path of the symlink to return stats for
+ * @param {object} lstats - The stats of the symlink
+ * @param {function} callback
+ */
+function symlinkStat (fs, path, lstats, callback) {
+ call.safe(fs.stat, path, (err, stats) => {
+ if (err) {
+ // The symlink is broken, so return the stats for the link itself
+ return callback(null, lstats);
+ }
+
+ try {
+ // Return the stats for the resolved symlink target,
+ // and override the `isSymbolicLink` method to indicate that it's a symlink
+ stats.isSymbolicLink = () => true;
+ }
+ catch (err2) {
+ // Setting stats.isSymbolicLink threw an error
+ // (probably because fs.stat returned an invalid result)
+ return callback(err2);
+ }
+
+ callback(null, stats);
+ });
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/stream/index.js b/node_modules/@mrmlnc/readdir-enhanced/lib/stream/index.js
new file mode 100644
index 0000000..22a9609
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/stream/index.js
@@ -0,0 +1,25 @@
+'use strict';
+
+module.exports = readdirStream;
+
+const DirectoryReader = require('../directory-reader');
+
+let streamFacade = {
+ fs: require('fs'),
+ forEach: require('../async/for-each'),
+ async: true
+};
+
+/**
+ * Returns the {@link stream.Readable} of an asynchronous {@link DirectoryReader}.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @param {object} internalOptions
+ */
+function readdirStream (dir, options, internalOptions) {
+ internalOptions.facade = streamFacade;
+
+ let reader = new DirectoryReader(dir, options, internalOptions);
+ return reader.stream;
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/sync/for-each.js b/node_modules/@mrmlnc/readdir-enhanced/lib/sync/for-each.js
new file mode 100644
index 0000000..c5ec088
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/sync/for-each.js
@@ -0,0 +1,22 @@
+'use strict';
+
+module.exports = syncForEach;
+
+/**
+ * A facade that allows {@link Array.forEach} to be called as though it were asynchronous.
+ *
+ * @param {array} array - The array to iterate over
+ * @param {function} iterator - The function to call for each item in the array
+ * @param {function} done - The function to call when all iterators have completed
+ */
+function syncForEach (array, iterator, done) {
+ array.forEach(item => {
+ iterator(item, () => {
+ // Note: No error-handling here because this is currently only ever called
+ // by DirectoryReader, which never passes an `error` parameter to the callback.
+ // Instead, DirectoryReader emits an "error" event if an error occurs.
+ });
+ });
+
+ done();
+}
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/sync/fs.js b/node_modules/@mrmlnc/readdir-enhanced/lib/sync/fs.js
new file mode 100644
index 0000000..3aada77
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/sync/fs.js
@@ -0,0 +1,64 @@
+'use strict';
+
+const fs = require('fs');
+const call = require('../call');
+
+/**
+ * A facade around {@link fs.readdirSync} that allows it to be called
+ * the same way as {@link fs.readdir}.
+ *
+ * @param {string} dir
+ * @param {function} callback
+ */
+exports.readdir = function (dir, callback) {
+ // Make sure the callback is only called once
+ callback = call.once(callback);
+
+ try {
+ let items = fs.readdirSync(dir);
+ callback(null, items);
+ }
+ catch (err) {
+ callback(err);
+ }
+};
+
+/**
+ * A facade around {@link fs.statSync} that allows it to be called
+ * the same way as {@link fs.stat}.
+ *
+ * @param {string} path
+ * @param {function} callback
+ */
+exports.stat = function (path, callback) {
+ // Make sure the callback is only called once
+ callback = call.once(callback);
+
+ try {
+ let stats = fs.statSync(path);
+ callback(null, stats);
+ }
+ catch (err) {
+ callback(err);
+ }
+};
+
+/**
+ * A facade around {@link fs.lstatSync} that allows it to be called
+ * the same way as {@link fs.lstat}.
+ *
+ * @param {string} path
+ * @param {function} callback
+ */
+exports.lstat = function (path, callback) {
+ // Make sure the callback is only called once
+ callback = call.once(callback);
+
+ try {
+ let stats = fs.lstatSync(path);
+ callback(null, stats);
+ }
+ catch (err) {
+ callback(err);
+ }
+};
diff --git a/node_modules/@mrmlnc/readdir-enhanced/lib/sync/index.js b/node_modules/@mrmlnc/readdir-enhanced/lib/sync/index.js
new file mode 100644
index 0000000..60243a1
--- /dev/null
+++ b/node_modules/@mrmlnc/readdir-enhanced/lib/sync/index.js
@@ -0,0 +1,34 @@
+'use strict';
+
+module.exports = readdirSync;
+
+const DirectoryReader = require('../directory-reader');
+
+let syncFacade = {
+ fs: require('./fs'),
+ forEach: require('./for-each'),
+ sync: true
+};
+
+/**
+ * Returns the buffered output from a synchronous {@link DirectoryReader}.
+ *
+ * @param {string} dir
+ * @param {object} [options]
+ * @param {object} internalOptions
+ */
+function readdirSync (dir, options, internalOptions) {
+ internalOptions.facade = syncFacade;
+
+ let reader = new DirectoryReader(dir, options, internalOptions);
+ let stream = reader.stream;
+
+ let results = [];
+ let data = stream.read();
+ while (data !== null) {
+ results.push(data);
+ data = stream.read();
+ }
+
+ return results;
+}