glob-all.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. var util = require("util");
  2. var Glob = require("glob").Glob;
  3. var EventEmitter = require("events").EventEmitter;
  4. // helper class to store and compare glob results
  5. function File(pattern1, patternId1, path1, fileId1) {
  6. this.pattern = pattern1;
  7. this.patternId = patternId1;
  8. this.path = path1;
  9. this.fileId = fileId1;
  10. this.include = true;
  11. while (this.pattern.charAt(0) === "!") {
  12. this.include = !this.include;
  13. this.pattern = this.pattern.substr(1);
  14. }
  15. }
  16. File.prototype.stars = /((\/\*\*)?\/\*)?\.(\w+)$/;
  17. // strip stars and compare pattern length
  18. // longest length wins
  19. File.prototype.compare = function(other) {
  20. var p1 = this.pattern.replace(this.stars, "");
  21. var p2 = other.pattern.replace(this.stars, "");
  22. if (p1.length > p2.length) {
  23. return this;
  24. } else {
  25. return other;
  26. }
  27. };
  28. File.prototype.toString = function() {
  29. return this.path + " (" + this.patternId + ": " + this.fileId + ": " + this.pattern + ")";
  30. };
  31. // using standard node inheritance
  32. util.inherits(GlobAll, EventEmitter);
  33. // allows the use arrays with "node-glob"
  34. // interatively combines the resulting arrays
  35. // api is exactly the same
  36. function GlobAll(sync, patterns, opts, callback) {
  37. if (opts == null) {
  38. opts = {};
  39. }
  40. EventEmitter.call(this);
  41. // init array
  42. if (typeof patterns === "string") {
  43. patterns = [patterns];
  44. }
  45. if (!(patterns instanceof Array)) {
  46. return (typeof callback === "function") ? callback("Invalid input") : null;
  47. }
  48. // use copy of array
  49. this.patterns = patterns.slice();
  50. // no opts provided
  51. if (typeof opts === "function") {
  52. callback = opts;
  53. opts = {};
  54. }
  55. // allow sync+nocallback or async+callback
  56. if (sync !== (typeof callback !== "function")) {
  57. throw new Error("should" + (sync ? " not" : "") + " have callback");
  58. }
  59. // all globs share the same stat cache
  60. this.statCache = opts.statCache = opts.statCache || {};
  61. opts.sync = sync;
  62. this.opts = opts;
  63. this.set = {};
  64. this.results = null;
  65. this.globs = [];
  66. this.callback = callback;
  67. // bound functions
  68. this.globbedOne = this.globbedOne.bind(this);
  69. }
  70. GlobAll.prototype.run = function() {
  71. this.globNext();
  72. return this.results;
  73. };
  74. GlobAll.prototype.globNext = function() {
  75. var g, pattern, include = true;
  76. if (this.patterns.length === 0) {
  77. return this.globbedAll();
  78. }
  79. pattern = this.patterns[0]; // peek!
  80. // check whether this is an exclude pattern and
  81. // strip the leading ! if it is
  82. if (pattern.charAt(0) === "!") {
  83. include = false;
  84. pattern = pattern.substr(1);
  85. }
  86. // run
  87. if (this.opts.sync) {
  88. // sync - callback straight away
  89. g = new Glob(pattern, this.opts);
  90. this.globs.push(g);
  91. this.globbedOne(null, include, g.found);
  92. } else {
  93. // async
  94. var self = this;
  95. g = new Glob(pattern, this.opts, function(err, files) {
  96. self.globbedOne(err, include, files);
  97. });
  98. this.globs.push(g);
  99. }
  100. };
  101. // collect results
  102. GlobAll.prototype.globbedOne = function(err, include, files) {
  103. // handle callback error early
  104. if (err) {
  105. if (!this.callback) {
  106. this.emit("error", err);
  107. }
  108. this.removeAllListeners();
  109. if (this.callback) {
  110. this.callback(err);
  111. }
  112. return;
  113. }
  114. var patternId = this.patterns.length;
  115. var pattern = this.patterns.shift();
  116. // insert each into the results set
  117. for (var fileId = 0; fileId < files.length; fileId++) {
  118. // convert to file instance
  119. var path = files[fileId];
  120. var f = new File(pattern, patternId, path, fileId);
  121. var existing = this.set[path];
  122. // new item
  123. if (!existing) {
  124. if (include) {
  125. this.set[path] = f;
  126. this.emit("match", path);
  127. }
  128. continue;
  129. }
  130. // compare or delete
  131. if (include) {
  132. this.set[path] = f.compare(existing);
  133. } else {
  134. delete this.set[path];
  135. }
  136. }
  137. // run next
  138. this.globNext();
  139. };
  140. GlobAll.prototype.globbedAll = function() {
  141. // map result set into an array
  142. var files = [];
  143. for (var k in this.set) {
  144. files.push(this.set[k]);
  145. }
  146. // sort files by index
  147. files.sort(function(a, b) {
  148. if (a.patternId < b.patternId) {
  149. return 1;
  150. }
  151. if (a.patternId > b.patternId) {
  152. return -1;
  153. }
  154. if (a.fileId >= b.fileId) {
  155. return 1;
  156. } else {
  157. return -1;
  158. }
  159. });
  160. // finally, convert back into a path string
  161. this.results = files.map(function(f) {
  162. return f.path;
  163. });
  164. this.emit("end");
  165. this.removeAllListeners();
  166. // return string paths
  167. if (!this.opts.sync) {
  168. this.callback(null, this.results);
  169. }
  170. return this.results;
  171. };
  172. // expose
  173. var globAll = function(array, opts, callback) {
  174. var g = new GlobAll(false, array, opts, callback);
  175. g.run(); //async, so results are empty
  176. return g;
  177. };
  178. // sync is actually the same function :)
  179. globAll.sync = function(array, opts) {
  180. return new GlobAll(true, array, opts).run();
  181. };
  182. module.exports = globAll;