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