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