3 const path = require('path');
4 const globToRegExp = require('glob-to-regexp');
6 module.exports = normalizeOptions;
8 let isWindows = /^win/.test(process.platform);
11 * @typedef {Object} FSFacade
12 * @property {fs.readdir} readdir
13 * @property {fs.stat} stat
14 * @property {fs.lstat} lstat
18 * Validates and normalizes the options argument
20 * @param {object} [options] - User-specified options, if any
21 * @param {object} internalOptions - Internal options that aren't part of the public API
23 * @param {number|boolean|function} [options.deep]
24 * The number of directories to recursively traverse. Any falsy value or negative number will
25 * default to zero, so only the top-level contents will be returned. Set to `true` or `Infinity`
26 * to traverse all subdirectories. Or provide a function that accepts a {@link fs.Stats} object
27 * and returns a truthy value if the directory's contents should be crawled.
29 * @param {function|string|RegExp} [options.filter]
30 * A function that accepts a {@link fs.Stats} object and returns a truthy value if the data should
31 * be returned. Or a RegExp or glob string pattern, to filter by file name.
33 * @param {string} [options.sep]
34 * The path separator to use. By default, the OS-specific separator will be used, but this can be
35 * set to a specific value to ensure consistency across platforms.
37 * @param {string} [options.basePath]
38 * The base path to prepend to each result. If empty, then all results will be relative to `dir`.
40 * @param {FSFacade} [options.fs]
41 * Synchronous or asynchronous facades for Node.js File System module
43 * @param {object} [internalOptions.facade]
44 * Synchronous or asynchronous facades for various methods, including for the Node.js File System module
46 * @param {boolean} [internalOptions.emit]
47 * Indicates whether the reader should emit "file", "directory", and "symlink" events
49 * @param {boolean} [internalOptions.stats]
50 * Indicates whether the reader should emit {@link fs.Stats} objects instead of path strings
54 function normalizeOptions (options, internalOptions) {
55 if (options === null || options === undefined) {
58 else if (typeof options !== 'object') {
59 throw new TypeError('options must be an object');
62 let recurseDepth, recurseFn, recurseRegExp, recurseGlob, deep = options.deep;
63 if (deep === null || deep === undefined) {
66 else if (typeof deep === 'boolean') {
67 recurseDepth = deep ? Infinity : 0;
69 else if (typeof deep === 'number') {
70 if (deep < 0 || isNaN(deep)) {
71 throw new Error('options.deep must be a positive number');
73 else if (Math.floor(deep) !== deep) {
74 throw new Error('options.deep must be an integer');
80 else if (typeof deep === 'function') {
81 recurseDepth = Infinity;
84 else if (deep instanceof RegExp) {
85 recurseDepth = Infinity;
88 else if (typeof deep === 'string' && deep.length > 0) {
89 recurseDepth = Infinity;
90 recurseGlob = globToRegExp(deep, { extended: true, globstar: true });
93 throw new TypeError('options.deep must be a boolean, number, function, regular expression, or glob pattern');
96 let filterFn, filterRegExp, filterGlob, filter = options.filter;
97 if (filter !== null && filter !== undefined) {
98 if (typeof filter === 'function') {
101 else if (filter instanceof RegExp) {
102 filterRegExp = filter;
104 else if (typeof filter === 'string' && filter.length > 0) {
105 filterGlob = globToRegExp(filter, { extended: true, globstar: true });
108 throw new TypeError('options.filter must be a function, regular expression, or glob pattern');
112 let sep = options.sep;
113 if (sep === null || sep === undefined) {
116 else if (typeof sep !== 'string') {
117 throw new TypeError('options.sep must be a string');
120 let basePath = options.basePath;
121 if (basePath === null || basePath === undefined) {
124 else if (typeof basePath === 'string') {
125 // Append a path separator to the basePath, if necessary
126 if (basePath && basePath.substr(-1) !== sep) {
131 throw new TypeError('options.basePath must be a string');
134 // Convert the basePath to POSIX (forward slashes)
135 // so that glob pattern matching works consistently, even on Windows
136 let posixBasePath = basePath;
137 if (posixBasePath && sep !== '/') {
138 posixBasePath = posixBasePath.replace(new RegExp('\\' + sep, 'g'), '/');
140 /* istanbul ignore if */
142 // Convert Windows root paths (C:\) and UNCs (\\) to POSIX root paths
143 posixBasePath = posixBasePath.replace(/^([a-zA-Z]\:\/|\/\/)/, '/');
147 // Determine which facade methods to use
149 if (options.fs === null || options.fs === undefined) {
150 // The user didn't provide their own facades, so use our internal ones
151 facade = internalOptions.facade;
153 else if (typeof options.fs === 'object') {
154 // Merge the internal facade methods with the user-provided `fs` facades
155 facade = Object.assign({}, internalOptions.facade);
156 facade.fs = Object.assign({}, internalOptions.facade.fs, options.fs);
159 throw new TypeError('options.fs must be an object');
174 emit: !!internalOptions.emit,
175 stats: !!internalOptions.stats,