You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
13 KiB
387 lines
13 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.GlobStream = exports.GlobWalker = exports.GlobUtil = void 0; |
|
/** |
|
* Single-use utility classes to provide functionality to the {@link Glob} |
|
* methods. |
|
* |
|
* @module |
|
*/ |
|
const minipass_1 = require("minipass"); |
|
const ignore_js_1 = require("./ignore.js"); |
|
const processor_js_1 = require("./processor.js"); |
|
const makeIgnore = (ignore, opts) => typeof ignore === 'string' ? new ignore_js_1.Ignore([ignore], opts) |
|
: Array.isArray(ignore) ? new ignore_js_1.Ignore(ignore, opts) |
|
: ignore; |
|
/** |
|
* basic walking utilities that all the glob walker types use |
|
*/ |
|
class GlobUtil { |
|
path; |
|
patterns; |
|
opts; |
|
seen = new Set(); |
|
paused = false; |
|
aborted = false; |
|
#onResume = []; |
|
#ignore; |
|
#sep; |
|
signal; |
|
maxDepth; |
|
includeChildMatches; |
|
constructor(patterns, path, opts) { |
|
this.patterns = patterns; |
|
this.path = path; |
|
this.opts = opts; |
|
this.#sep = !opts.posix && opts.platform === 'win32' ? '\\' : '/'; |
|
this.includeChildMatches = opts.includeChildMatches !== false; |
|
if (opts.ignore || !this.includeChildMatches) { |
|
this.#ignore = makeIgnore(opts.ignore ?? [], opts); |
|
if (!this.includeChildMatches && |
|
typeof this.#ignore.add !== 'function') { |
|
const m = 'cannot ignore child matches, ignore lacks add() method.'; |
|
throw new Error(m); |
|
} |
|
} |
|
// ignore, always set with maxDepth, but it's optional on the |
|
// GlobOptions type |
|
/* c8 ignore start */ |
|
this.maxDepth = opts.maxDepth || Infinity; |
|
/* c8 ignore stop */ |
|
if (opts.signal) { |
|
this.signal = opts.signal; |
|
this.signal.addEventListener('abort', () => { |
|
this.#onResume.length = 0; |
|
}); |
|
} |
|
} |
|
#ignored(path) { |
|
return this.seen.has(path) || !!this.#ignore?.ignored?.(path); |
|
} |
|
#childrenIgnored(path) { |
|
return !!this.#ignore?.childrenIgnored?.(path); |
|
} |
|
// backpressure mechanism |
|
pause() { |
|
this.paused = true; |
|
} |
|
resume() { |
|
/* c8 ignore start */ |
|
if (this.signal?.aborted) |
|
return; |
|
/* c8 ignore stop */ |
|
this.paused = false; |
|
let fn = undefined; |
|
while (!this.paused && (fn = this.#onResume.shift())) { |
|
fn(); |
|
} |
|
} |
|
onResume(fn) { |
|
if (this.signal?.aborted) |
|
return; |
|
/* c8 ignore start */ |
|
if (!this.paused) { |
|
fn(); |
|
} |
|
else { |
|
/* c8 ignore stop */ |
|
this.#onResume.push(fn); |
|
} |
|
} |
|
// do the requisite realpath/stat checking, and return the path |
|
// to add or undefined to filter it out. |
|
async matchCheck(e, ifDir) { |
|
if (ifDir && this.opts.nodir) |
|
return undefined; |
|
let rpc; |
|
if (this.opts.realpath) { |
|
rpc = e.realpathCached() || (await e.realpath()); |
|
if (!rpc) |
|
return undefined; |
|
e = rpc; |
|
} |
|
const needStat = e.isUnknown() || this.opts.stat; |
|
const s = needStat ? await e.lstat() : e; |
|
if (this.opts.follow && this.opts.nodir && s?.isSymbolicLink()) { |
|
const target = await s.realpath(); |
|
/* c8 ignore start */ |
|
if (target && (target.isUnknown() || this.opts.stat)) { |
|
await target.lstat(); |
|
} |
|
/* c8 ignore stop */ |
|
} |
|
return this.matchCheckTest(s, ifDir); |
|
} |
|
matchCheckTest(e, ifDir) { |
|
return (e && |
|
(this.maxDepth === Infinity || e.depth() <= this.maxDepth) && |
|
(!ifDir || e.canReaddir()) && |
|
(!this.opts.nodir || !e.isDirectory()) && |
|
(!this.opts.nodir || |
|
!this.opts.follow || |
|
!e.isSymbolicLink() || |
|
!e.realpathCached()?.isDirectory()) && |
|
!this.#ignored(e)) ? |
|
e |
|
: undefined; |
|
} |
|
matchCheckSync(e, ifDir) { |
|
if (ifDir && this.opts.nodir) |
|
return undefined; |
|
let rpc; |
|
if (this.opts.realpath) { |
|
rpc = e.realpathCached() || e.realpathSync(); |
|
if (!rpc) |
|
return undefined; |
|
e = rpc; |
|
} |
|
const needStat = e.isUnknown() || this.opts.stat; |
|
const s = needStat ? e.lstatSync() : e; |
|
if (this.opts.follow && this.opts.nodir && s?.isSymbolicLink()) { |
|
const target = s.realpathSync(); |
|
if (target && (target?.isUnknown() || this.opts.stat)) { |
|
target.lstatSync(); |
|
} |
|
} |
|
return this.matchCheckTest(s, ifDir); |
|
} |
|
matchFinish(e, absolute) { |
|
if (this.#ignored(e)) |
|
return; |
|
// we know we have an ignore if this is false, but TS doesn't |
|
if (!this.includeChildMatches && this.#ignore?.add) { |
|
const ign = `${e.relativePosix()}/**`; |
|
this.#ignore.add(ign); |
|
} |
|
const abs = this.opts.absolute === undefined ? absolute : this.opts.absolute; |
|
this.seen.add(e); |
|
const mark = this.opts.mark && e.isDirectory() ? this.#sep : ''; |
|
// ok, we have what we need! |
|
if (this.opts.withFileTypes) { |
|
this.matchEmit(e); |
|
} |
|
else if (abs) { |
|
const abs = this.opts.posix ? e.fullpathPosix() : e.fullpath(); |
|
this.matchEmit(abs + mark); |
|
} |
|
else { |
|
const rel = this.opts.posix ? e.relativePosix() : e.relative(); |
|
const pre = this.opts.dotRelative && !rel.startsWith('..' + this.#sep) ? |
|
'.' + this.#sep |
|
: ''; |
|
this.matchEmit(!rel ? '.' + mark : pre + rel + mark); |
|
} |
|
} |
|
async match(e, absolute, ifDir) { |
|
const p = await this.matchCheck(e, ifDir); |
|
if (p) |
|
this.matchFinish(p, absolute); |
|
} |
|
matchSync(e, absolute, ifDir) { |
|
const p = this.matchCheckSync(e, ifDir); |
|
if (p) |
|
this.matchFinish(p, absolute); |
|
} |
|
walkCB(target, patterns, cb) { |
|
/* c8 ignore start */ |
|
if (this.signal?.aborted) |
|
cb(); |
|
/* c8 ignore stop */ |
|
this.walkCB2(target, patterns, new processor_js_1.Processor(this.opts), cb); |
|
} |
|
walkCB2(target, patterns, processor, cb) { |
|
if (this.#childrenIgnored(target)) |
|
return cb(); |
|
if (this.signal?.aborted) |
|
cb(); |
|
if (this.paused) { |
|
this.onResume(() => this.walkCB2(target, patterns, processor, cb)); |
|
return; |
|
} |
|
processor.processPatterns(target, patterns); |
|
// done processing. all of the above is sync, can be abstracted out. |
|
// subwalks is a map of paths to the entry filters they need |
|
// matches is a map of paths to [absolute, ifDir] tuples. |
|
let tasks = 1; |
|
const next = () => { |
|
if (--tasks === 0) |
|
cb(); |
|
}; |
|
for (const [m, absolute, ifDir] of processor.matches.entries()) { |
|
if (this.#ignored(m)) |
|
continue; |
|
tasks++; |
|
this.match(m, absolute, ifDir).then(() => next()); |
|
} |
|
for (const t of processor.subwalkTargets()) { |
|
if (this.maxDepth !== Infinity && t.depth() >= this.maxDepth) { |
|
continue; |
|
} |
|
tasks++; |
|
const childrenCached = t.readdirCached(); |
|
if (t.calledReaddir()) |
|
this.walkCB3(t, childrenCached, processor, next); |
|
else { |
|
t.readdirCB((_, entries) => this.walkCB3(t, entries, processor, next), true); |
|
} |
|
} |
|
next(); |
|
} |
|
walkCB3(target, entries, processor, cb) { |
|
processor = processor.filterEntries(target, entries); |
|
let tasks = 1; |
|
const next = () => { |
|
if (--tasks === 0) |
|
cb(); |
|
}; |
|
for (const [m, absolute, ifDir] of processor.matches.entries()) { |
|
if (this.#ignored(m)) |
|
continue; |
|
tasks++; |
|
this.match(m, absolute, ifDir).then(() => next()); |
|
} |
|
for (const [target, patterns] of processor.subwalks.entries()) { |
|
tasks++; |
|
this.walkCB2(target, patterns, processor.child(), next); |
|
} |
|
next(); |
|
} |
|
walkCBSync(target, patterns, cb) { |
|
/* c8 ignore start */ |
|
if (this.signal?.aborted) |
|
cb(); |
|
/* c8 ignore stop */ |
|
this.walkCB2Sync(target, patterns, new processor_js_1.Processor(this.opts), cb); |
|
} |
|
walkCB2Sync(target, patterns, processor, cb) { |
|
if (this.#childrenIgnored(target)) |
|
return cb(); |
|
if (this.signal?.aborted) |
|
cb(); |
|
if (this.paused) { |
|
this.onResume(() => this.walkCB2Sync(target, patterns, processor, cb)); |
|
return; |
|
} |
|
processor.processPatterns(target, patterns); |
|
// done processing. all of the above is sync, can be abstracted out. |
|
// subwalks is a map of paths to the entry filters they need |
|
// matches is a map of paths to [absolute, ifDir] tuples. |
|
let tasks = 1; |
|
const next = () => { |
|
if (--tasks === 0) |
|
cb(); |
|
}; |
|
for (const [m, absolute, ifDir] of processor.matches.entries()) { |
|
if (this.#ignored(m)) |
|
continue; |
|
this.matchSync(m, absolute, ifDir); |
|
} |
|
for (const t of processor.subwalkTargets()) { |
|
if (this.maxDepth !== Infinity && t.depth() >= this.maxDepth) { |
|
continue; |
|
} |
|
tasks++; |
|
const children = t.readdirSync(); |
|
this.walkCB3Sync(t, children, processor, next); |
|
} |
|
next(); |
|
} |
|
walkCB3Sync(target, entries, processor, cb) { |
|
processor = processor.filterEntries(target, entries); |
|
let tasks = 1; |
|
const next = () => { |
|
if (--tasks === 0) |
|
cb(); |
|
}; |
|
for (const [m, absolute, ifDir] of processor.matches.entries()) { |
|
if (this.#ignored(m)) |
|
continue; |
|
this.matchSync(m, absolute, ifDir); |
|
} |
|
for (const [target, patterns] of processor.subwalks.entries()) { |
|
tasks++; |
|
this.walkCB2Sync(target, patterns, processor.child(), next); |
|
} |
|
next(); |
|
} |
|
} |
|
exports.GlobUtil = GlobUtil; |
|
class GlobWalker extends GlobUtil { |
|
matches = new Set(); |
|
constructor(patterns, path, opts) { |
|
super(patterns, path, opts); |
|
} |
|
matchEmit(e) { |
|
this.matches.add(e); |
|
} |
|
async walk() { |
|
if (this.signal?.aborted) |
|
throw this.signal.reason; |
|
if (this.path.isUnknown()) { |
|
await this.path.lstat(); |
|
} |
|
await new Promise((res, rej) => { |
|
this.walkCB(this.path, this.patterns, () => { |
|
if (this.signal?.aborted) { |
|
rej(this.signal.reason); |
|
} |
|
else { |
|
res(this.matches); |
|
} |
|
}); |
|
}); |
|
return this.matches; |
|
} |
|
walkSync() { |
|
if (this.signal?.aborted) |
|
throw this.signal.reason; |
|
if (this.path.isUnknown()) { |
|
this.path.lstatSync(); |
|
} |
|
// nothing for the callback to do, because this never pauses |
|
this.walkCBSync(this.path, this.patterns, () => { |
|
if (this.signal?.aborted) |
|
throw this.signal.reason; |
|
}); |
|
return this.matches; |
|
} |
|
} |
|
exports.GlobWalker = GlobWalker; |
|
class GlobStream extends GlobUtil { |
|
results; |
|
constructor(patterns, path, opts) { |
|
super(patterns, path, opts); |
|
this.results = new minipass_1.Minipass({ |
|
signal: this.signal, |
|
objectMode: true, |
|
}); |
|
this.results.on('drain', () => this.resume()); |
|
this.results.on('resume', () => this.resume()); |
|
} |
|
matchEmit(e) { |
|
this.results.write(e); |
|
if (!this.results.flowing) |
|
this.pause(); |
|
} |
|
stream() { |
|
const target = this.path; |
|
if (target.isUnknown()) { |
|
target.lstat().then(() => { |
|
this.walkCB(target, this.patterns, () => this.results.end()); |
|
}); |
|
} |
|
else { |
|
this.walkCB(target, this.patterns, () => this.results.end()); |
|
} |
|
return this.results; |
|
} |
|
streamSync() { |
|
if (this.path.isUnknown()) { |
|
this.path.lstatSync(); |
|
} |
|
this.walkCBSync(this.path, this.patterns, () => this.results.end()); |
|
return this.results; |
|
} |
|
} |
|
exports.GlobStream = GlobStream; |
|
//# sourceMappingURL=walker.js.map
|