You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
5.6 KiB

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