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