walker.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /**
  2. * Single-use utility classes to provide functionality to the {@link Glob}
  3. * methods.
  4. *
  5. * @module
  6. */
  7. import Minipass from 'minipass';
  8. import { Ignore } from './ignore.js';
  9. import { Processor } from './processor.js';
  10. const makeIgnore = (ignore, opts) => typeof ignore === 'string'
  11. ? new Ignore([ignore], opts)
  12. : Array.isArray(ignore)
  13. ? new Ignore(ignore, opts)
  14. : /* c8 ignore start */
  15. ignore;
  16. /* c8 ignore stop */
  17. /**
  18. * basic walking utilities that all the glob walker types use
  19. */
  20. export class GlobUtil {
  21. path;
  22. patterns;
  23. opts;
  24. seen = new Set();
  25. paused = false;
  26. aborted = false;
  27. #onResume = [];
  28. #ignore;
  29. #sep;
  30. signal;
  31. constructor(patterns, path, opts) {
  32. this.patterns = patterns;
  33. this.path = path;
  34. this.opts = opts;
  35. this.#sep = opts.platform === 'win32' ? '\\' : '/';
  36. if (opts.ignore) {
  37. this.#ignore = makeIgnore(opts.ignore, opts);
  38. }
  39. if (opts.signal) {
  40. this.signal = opts.signal;
  41. this.signal.addEventListener('abort', () => {
  42. this.#onResume.length = 0;
  43. });
  44. }
  45. }
  46. #ignored(path) {
  47. return this.seen.has(path) || !!this.#ignore?.ignored(path);
  48. }
  49. #childrenIgnored(path) {
  50. return !!this.#ignore?.childrenIgnored(path);
  51. }
  52. // backpressure mechanism
  53. pause() {
  54. this.paused = true;
  55. }
  56. resume() {
  57. /* c8 ignore start */
  58. if (this.signal?.aborted)
  59. return;
  60. /* c8 ignore stop */
  61. this.paused = false;
  62. let fn = undefined;
  63. while (!this.paused && (fn = this.#onResume.shift())) {
  64. fn();
  65. }
  66. }
  67. onResume(fn) {
  68. if (this.signal?.aborted)
  69. return;
  70. /* c8 ignore start */
  71. if (!this.paused) {
  72. fn();
  73. }
  74. else {
  75. /* c8 ignore stop */
  76. this.#onResume.push(fn);
  77. }
  78. }
  79. // do the requisite realpath/stat checking, and return the path
  80. // to add or undefined to filter it out.
  81. async matchCheck(e, ifDir) {
  82. if (ifDir && this.opts.nodir)
  83. return undefined;
  84. let rpc;
  85. if (this.opts.realpath) {
  86. rpc = e.realpathCached() || (await e.realpath());
  87. if (!rpc)
  88. return undefined;
  89. e = rpc;
  90. }
  91. const needStat = e.isUnknown();
  92. return this.matchCheckTest(needStat ? await e.lstat() : e, ifDir);
  93. }
  94. matchCheckTest(e, ifDir) {
  95. return e &&
  96. !this.#ignored(e) &&
  97. (!ifDir || e.canReaddir()) &&
  98. (!this.opts.nodir || !e.isDirectory())
  99. ? e
  100. : undefined;
  101. }
  102. matchCheckSync(e, ifDir) {
  103. if (ifDir && this.opts.nodir)
  104. return undefined;
  105. let rpc;
  106. if (this.opts.realpath) {
  107. rpc = e.realpathCached() || e.realpathSync();
  108. if (!rpc)
  109. return undefined;
  110. e = rpc;
  111. }
  112. const needStat = e.isUnknown();
  113. return this.matchCheckTest(needStat ? e.lstatSync() : e, ifDir);
  114. }
  115. matchFinish(e, absolute) {
  116. if (this.#ignored(e))
  117. return;
  118. this.seen.add(e);
  119. const mark = this.opts.mark && e.isDirectory() ? this.#sep : '';
  120. // ok, we have what we need!
  121. if (this.opts.withFileTypes) {
  122. this.matchEmit(e);
  123. }
  124. else if (this.opts.absolute || absolute) {
  125. this.matchEmit(e.fullpath() + mark);
  126. }
  127. else {
  128. const rel = e.relative();
  129. this.matchEmit(!rel && mark ? './' : rel + mark);
  130. }
  131. }
  132. async match(e, absolute, ifDir) {
  133. const p = await this.matchCheck(e, ifDir);
  134. if (p)
  135. this.matchFinish(p, absolute);
  136. }
  137. matchSync(e, absolute, ifDir) {
  138. const p = this.matchCheckSync(e, ifDir);
  139. if (p)
  140. this.matchFinish(p, absolute);
  141. }
  142. walkCB(target, patterns, cb) {
  143. /* c8 ignore start */
  144. if (this.signal?.aborted)
  145. cb();
  146. /* c8 ignore stop */
  147. this.walkCB2(target, patterns, new Processor(this.opts), cb);
  148. }
  149. walkCB2(target, patterns, processor, cb) {
  150. if (this.#childrenIgnored(target))
  151. return cb();
  152. if (this.signal?.aborted)
  153. cb();
  154. if (this.paused) {
  155. this.onResume(() => this.walkCB2(target, patterns, processor, cb));
  156. return;
  157. }
  158. processor.processPatterns(target, patterns);
  159. // done processing. all of the above is sync, can be abstracted out.
  160. // subwalks is a map of paths to the entry filters they need
  161. // matches is a map of paths to [absolute, ifDir] tuples.
  162. let tasks = 1;
  163. const next = () => {
  164. if (--tasks === 0)
  165. cb();
  166. };
  167. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  168. if (this.#ignored(m))
  169. continue;
  170. tasks++;
  171. this.match(m, absolute, ifDir).then(() => next());
  172. }
  173. for (const t of processor.subwalkTargets()) {
  174. tasks++;
  175. const childrenCached = t.readdirCached();
  176. if (t.calledReaddir())
  177. this.walkCB3(t, childrenCached, processor, next);
  178. else {
  179. t.readdirCB((_, entries) => this.walkCB3(t, entries, processor, next), true);
  180. }
  181. }
  182. next();
  183. }
  184. walkCB3(target, entries, processor, cb) {
  185. processor = processor.filterEntries(target, entries);
  186. let tasks = 1;
  187. const next = () => {
  188. if (--tasks === 0)
  189. cb();
  190. };
  191. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  192. if (this.#ignored(m))
  193. continue;
  194. tasks++;
  195. this.match(m, absolute, ifDir).then(() => next());
  196. }
  197. for (const [target, patterns] of processor.subwalks.entries()) {
  198. tasks++;
  199. this.walkCB2(target, patterns, processor.child(), next);
  200. }
  201. next();
  202. }
  203. walkCBSync(target, patterns, cb) {
  204. /* c8 ignore start */
  205. if (this.signal?.aborted)
  206. cb();
  207. /* c8 ignore stop */
  208. this.walkCB2Sync(target, patterns, new Processor(this.opts), cb);
  209. }
  210. walkCB2Sync(target, patterns, processor, cb) {
  211. if (this.#childrenIgnored(target))
  212. return cb();
  213. if (this.signal?.aborted)
  214. cb();
  215. if (this.paused) {
  216. this.onResume(() => this.walkCB2Sync(target, patterns, processor, cb));
  217. return;
  218. }
  219. processor.processPatterns(target, patterns);
  220. // done processing. all of the above is sync, can be abstracted out.
  221. // subwalks is a map of paths to the entry filters they need
  222. // matches is a map of paths to [absolute, ifDir] tuples.
  223. let tasks = 1;
  224. const next = () => {
  225. if (--tasks === 0)
  226. cb();
  227. };
  228. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  229. if (this.#ignored(m))
  230. continue;
  231. this.matchSync(m, absolute, ifDir);
  232. }
  233. for (const t of processor.subwalkTargets()) {
  234. tasks++;
  235. const children = t.readdirSync();
  236. this.walkCB3Sync(t, children, processor, next);
  237. }
  238. next();
  239. }
  240. walkCB3Sync(target, entries, processor, cb) {
  241. processor = processor.filterEntries(target, entries);
  242. let tasks = 1;
  243. const next = () => {
  244. if (--tasks === 0)
  245. cb();
  246. };
  247. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  248. if (this.#ignored(m))
  249. continue;
  250. this.matchSync(m, absolute, ifDir);
  251. }
  252. for (const [target, patterns] of processor.subwalks.entries()) {
  253. tasks++;
  254. this.walkCB2Sync(target, patterns, processor.child(), next);
  255. }
  256. next();
  257. }
  258. }
  259. export class GlobWalker extends GlobUtil {
  260. matches;
  261. constructor(patterns, path, opts) {
  262. super(patterns, path, opts);
  263. this.matches = new Set();
  264. }
  265. matchEmit(e) {
  266. this.matches.add(e);
  267. }
  268. async walk() {
  269. if (this.signal?.aborted)
  270. throw this.signal.reason;
  271. const t = this.path.isUnknown() ? await this.path.lstat() : this.path;
  272. if (t) {
  273. await new Promise((res, rej) => {
  274. this.walkCB(t, this.patterns, () => {
  275. if (this.signal?.aborted) {
  276. rej(this.signal.reason);
  277. }
  278. else {
  279. res(this.matches);
  280. }
  281. });
  282. });
  283. }
  284. return this.matches;
  285. }
  286. walkSync() {
  287. if (this.signal?.aborted)
  288. throw this.signal.reason;
  289. const t = this.path.isUnknown() ? this.path.lstatSync() : this.path;
  290. // nothing for the callback to do, because this never pauses
  291. if (t) {
  292. this.walkCBSync(t, this.patterns, () => {
  293. if (this.signal?.aborted)
  294. throw this.signal.reason;
  295. });
  296. }
  297. return this.matches;
  298. }
  299. }
  300. export class GlobStream extends GlobUtil {
  301. results;
  302. constructor(patterns, path, opts) {
  303. super(patterns, path, opts);
  304. this.results = new Minipass({
  305. signal: this.signal,
  306. objectMode: true,
  307. });
  308. this.results.on('drain', () => this.resume());
  309. this.results.on('resume', () => this.resume());
  310. }
  311. matchEmit(e) {
  312. this.results.write(e);
  313. if (!this.results.flowing)
  314. this.pause();
  315. }
  316. stream() {
  317. const target = this.path;
  318. if (target.isUnknown()) {
  319. target.lstat().then(e => {
  320. if (e) {
  321. this.walkCB(target, this.patterns, () => this.results.end());
  322. }
  323. else {
  324. this.results.end();
  325. }
  326. });
  327. }
  328. else {
  329. this.walkCB(target, this.patterns, () => this.results.end());
  330. }
  331. return this.results;
  332. }
  333. streamSync() {
  334. const target = this.path.isUnknown()
  335. ? this.path.lstatSync()
  336. : this.path;
  337. if (target) {
  338. this.walkCBSync(target, this.patterns, () => this.results.end());
  339. }
  340. else {
  341. this.results.end();
  342. }
  343. return this.results;
  344. }
  345. }
  346. //# sourceMappingURL=walker.js.map