diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2019-10-06 21:37:45 +0200 | 
|---|---|---|
| committer | Dimitri Staessens <dimitri@ouroboros.rocks> | 2019-10-06 21:37:45 +0200 | 
| commit | 3c51c3be85bb0d1bdb87ea0d6632f1c256912f27 (patch) | |
| tree | c7ccc8279b12c4f7bdbbb4270d617e48f51722e4 /node_modules/@mrmlnc/readdir-enhanced/lib | |
| parent | 412c104bebc507bea9c94fd53b5bdc4b64cbfe31 (diff) | |
| download | website-3c51c3be85bb0d1bdb87ea0d6632f1c256912f27.tar.gz website-3c51c3be85bb0d1bdb87ea0d6632f1c256912f27.zip  | |
build: Add some required modules for node
Diffstat (limited to 'node_modules/@mrmlnc/readdir-enhanced/lib')
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; +}  | 
