globule.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /*
  2. * globule
  3. * https://github.com/cowboy/node-globule
  4. *
  5. * Copyright (c) 2018 "Cowboy" Ben Alman
  6. * Licensed under the MIT license.
  7. */
  8. 'use strict';
  9. var fs = require('fs');
  10. var path = require('path');
  11. var _ = require('lodash');
  12. var glob = require('glob');
  13. var minimatch = require('minimatch');
  14. // The module.
  15. var globule = exports;
  16. // Process specified wildcard glob patterns or filenames against a
  17. // callback, excluding and uniquing files in the result set.
  18. function processPatterns(patterns, options, fn) {
  19. var result = [];
  20. _.each(patterns, function(pattern) {
  21. // The first character is not ! (inclusion). Add all matching filepaths
  22. // to the result set.
  23. if (pattern.indexOf('!') !== 0) {
  24. result = _.union(result, fn(pattern));
  25. return;
  26. }
  27. // The first character is ! (exclusion). Remove any filepaths from the
  28. // result set that match this pattern, sans leading !.
  29. var filterFn = minimatch.filter(pattern.slice(1), options);
  30. result = _.filter(result, function(filepath) {
  31. return !filterFn(filepath);
  32. });
  33. });
  34. return result;
  35. }
  36. // Normalize paths to be unix-style.
  37. var pathSeparatorRe = /[\/\\]/g;
  38. function normalizePath(path) {
  39. return path.replace(pathSeparatorRe, '/');
  40. }
  41. // Match a filepath or filepaths against one or more wildcard patterns. Returns
  42. // all matching filepaths. This behaves just like minimatch.match, but supports
  43. // any number of patterns.
  44. globule.match = function(patterns, filepaths, options) {
  45. // Return empty set if either patterns or filepaths was omitted.
  46. if (patterns == null || filepaths == null) { return []; }
  47. // Normalize patterns and filepaths to flattened arrays.
  48. patterns = _.isArray(patterns) ? _.flattenDeep(patterns) : [patterns];
  49. filepaths = _.isArray(filepaths) ? _.flattenDeep(filepaths) : [filepaths];
  50. // Return empty set if there are no patterns or filepaths.
  51. if (patterns.length === 0 || filepaths.length === 0) { return []; }
  52. // Return all matching filepaths.
  53. return processPatterns(patterns, options, function(pattern) {
  54. return minimatch.match(filepaths, pattern, options || {});
  55. });
  56. };
  57. // Match a filepath or filepaths against one or more wildcard patterns. Returns
  58. // true if any of the patterns match.
  59. globule.isMatch = function() {
  60. return globule.match.apply(null, arguments).length > 0;
  61. };
  62. // Return an array of all file paths that match the given wildcard patterns.
  63. globule.find = function() {
  64. var args = _.toArray(arguments);
  65. // If the last argument is an options object, remove it from args.
  66. var options = _.isPlainObject(args[args.length - 1]) ? args.pop() : {};
  67. // If options.src was specified, use it. Otherwise, use all non-options
  68. // arguments. Flatten nested arrays.
  69. var patterns;
  70. if (options.src) {
  71. patterns = _.isArray(options.src) ? _.flattenDeep(options.src) : [options.src];
  72. } else {
  73. patterns = _.flattenDeep(args);
  74. }
  75. // Return empty set if there are no patterns.
  76. if (patterns.length === 0) { return []; }
  77. var srcBase = options.srcBase || options.cwd;
  78. // Create glob-specific options object.
  79. var globOptions = _.extend({}, options);
  80. if (srcBase) {
  81. globOptions.cwd = srcBase;
  82. }
  83. // Get all matching filepaths.
  84. var matches = processPatterns(patterns, options, function(pattern) {
  85. return glob.sync(pattern, globOptions);
  86. });
  87. // If srcBase and prefixBase were specified, prefix srcBase to matched paths.
  88. if (srcBase && options.prefixBase) {
  89. matches = matches.map(function(filepath) {
  90. return normalizePath(path.join(srcBase, filepath));
  91. });
  92. }
  93. // Filter result set?
  94. if (options.filter) {
  95. matches = matches.filter(function(filepath) {
  96. // If srcBase was specified but prefixBase was NOT, prefix srcBase
  97. // temporarily, for filtering.
  98. if (srcBase && !options.prefixBase) {
  99. filepath = normalizePath(path.join(srcBase, filepath));
  100. }
  101. try {
  102. if (_.isFunction(options.filter)) {
  103. return options.filter(filepath, options);
  104. } else {
  105. // If the file is of the right type and exists, this should work.
  106. return fs.statSync(filepath)[options.filter]();
  107. }
  108. } catch(err) {
  109. // Otherwise, it's probably not the right type.
  110. return false;
  111. }
  112. });
  113. }
  114. return matches;
  115. };
  116. var extDotRe = {
  117. first: /(\.[^\/]*)?$/,
  118. last: /(\.[^\/\.]*)?$/,
  119. };
  120. function rename(dest, options) {
  121. // Flatten path?
  122. if (options.flatten) {
  123. dest = path.basename(dest);
  124. }
  125. // Change the extension?
  126. if (options.ext) {
  127. dest = dest.replace(extDotRe[options.extDot], options.ext);
  128. }
  129. // Join dest and destBase?
  130. if (options.destBase) {
  131. dest = path.join(options.destBase, dest);
  132. }
  133. return dest;
  134. }
  135. // Build a mapping of src-dest filepaths from the given set of filepaths.
  136. globule.mapping = function(filepaths, options) {
  137. // Return empty set if filepaths was omitted.
  138. if (filepaths == null) { return []; }
  139. options = _.defaults({}, options, {
  140. extDot: 'first',
  141. rename: rename,
  142. });
  143. var files = [];
  144. var fileByDest = {};
  145. // Find all files matching pattern, using passed-in options.
  146. filepaths.forEach(function(src) {
  147. // Generate destination filename.
  148. var dest = options.rename(src, options);
  149. // Prepend srcBase to all src paths.
  150. if (options.srcBase) {
  151. src = path.join(options.srcBase, src);
  152. }
  153. // Normalize filepaths to be unix-style.
  154. dest = normalizePath(dest);
  155. src = normalizePath(src);
  156. // Map correct src path to dest path.
  157. if (fileByDest[dest]) {
  158. // If dest already exists, push this src onto that dest's src array.
  159. fileByDest[dest].src.push(src);
  160. } else {
  161. // Otherwise create a new src-dest file mapping object.
  162. files.push({
  163. src: [src],
  164. dest: dest,
  165. });
  166. // And store a reference for later use.
  167. fileByDest[dest] = files[files.length - 1];
  168. }
  169. });
  170. return files;
  171. };
  172. // Return a mapping of src-dest filepaths from files matching the given
  173. // wildcard patterns.
  174. globule.findMapping = function() {
  175. var args = _.toArray(arguments);
  176. // If the last argument is an options object, remove it from args.
  177. var options = _.isPlainObject(args[args.length - 1]) ? args.pop() : {};
  178. // Generate mapping from found filepaths.
  179. return globule.mapping(globule.find(args, options), options);
  180. };