'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, }; }