301 lines
10 KiB
JavaScript
301 lines
10 KiB
JavaScript
|
"use strict";
|
||
|
// synchronous utility for filtering entries and calculating subwalks
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.Processor = exports.SubWalks = exports.MatchRecord = exports.HasWalkedCache = void 0;
|
||
|
const minimatch_1 = require("minimatch");
|
||
|
/**
|
||
|
* A cache of which patterns have been processed for a given Path
|
||
|
*/
|
||
|
class HasWalkedCache {
|
||
|
store;
|
||
|
constructor(store = new Map()) {
|
||
|
this.store = store;
|
||
|
}
|
||
|
copy() {
|
||
|
return new HasWalkedCache(new Map(this.store));
|
||
|
}
|
||
|
hasWalked(target, pattern) {
|
||
|
return this.store.get(target.fullpath())?.has(pattern.globString());
|
||
|
}
|
||
|
storeWalked(target, pattern) {
|
||
|
const fullpath = target.fullpath();
|
||
|
const cached = this.store.get(fullpath);
|
||
|
if (cached)
|
||
|
cached.add(pattern.globString());
|
||
|
else
|
||
|
this.store.set(fullpath, new Set([pattern.globString()]));
|
||
|
}
|
||
|
}
|
||
|
exports.HasWalkedCache = HasWalkedCache;
|
||
|
/**
|
||
|
* A record of which paths have been matched in a given walk step,
|
||
|
* and whether they only are considered a match if they are a directory,
|
||
|
* and whether their absolute or relative path should be returned.
|
||
|
*/
|
||
|
class MatchRecord {
|
||
|
store = new Map();
|
||
|
add(target, absolute, ifDir) {
|
||
|
const n = (absolute ? 2 : 0) | (ifDir ? 1 : 0);
|
||
|
const current = this.store.get(target);
|
||
|
this.store.set(target, current === undefined ? n : n & current);
|
||
|
}
|
||
|
// match, absolute, ifdir
|
||
|
entries() {
|
||
|
return [...this.store.entries()].map(([path, n]) => [
|
||
|
path,
|
||
|
!!(n & 2),
|
||
|
!!(n & 1),
|
||
|
]);
|
||
|
}
|
||
|
}
|
||
|
exports.MatchRecord = MatchRecord;
|
||
|
/**
|
||
|
* A collection of patterns that must be processed in a subsequent step
|
||
|
* for a given path.
|
||
|
*/
|
||
|
class SubWalks {
|
||
|
store = new Map();
|
||
|
add(target, pattern) {
|
||
|
if (!target.canReaddir()) {
|
||
|
return;
|
||
|
}
|
||
|
const subs = this.store.get(target);
|
||
|
if (subs) {
|
||
|
if (!subs.find(p => p.globString() === pattern.globString())) {
|
||
|
subs.push(pattern);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
this.store.set(target, [pattern]);
|
||
|
}
|
||
|
get(target) {
|
||
|
const subs = this.store.get(target);
|
||
|
/* c8 ignore start */
|
||
|
if (!subs) {
|
||
|
throw new Error('attempting to walk unknown path');
|
||
|
}
|
||
|
/* c8 ignore stop */
|
||
|
return subs;
|
||
|
}
|
||
|
entries() {
|
||
|
return this.keys().map(k => [k, this.store.get(k)]);
|
||
|
}
|
||
|
keys() {
|
||
|
return [...this.store.keys()].filter(t => t.canReaddir());
|
||
|
}
|
||
|
}
|
||
|
exports.SubWalks = SubWalks;
|
||
|
/**
|
||
|
* The class that processes patterns for a given path.
|
||
|
*
|
||
|
* Handles child entry filtering, and determining whether a path's
|
||
|
* directory contents must be read.
|
||
|
*/
|
||
|
class Processor {
|
||
|
hasWalkedCache;
|
||
|
matches = new MatchRecord();
|
||
|
subwalks = new SubWalks();
|
||
|
patterns;
|
||
|
follow;
|
||
|
dot;
|
||
|
opts;
|
||
|
constructor(opts, hasWalkedCache) {
|
||
|
this.opts = opts;
|
||
|
this.follow = !!opts.follow;
|
||
|
this.dot = !!opts.dot;
|
||
|
this.hasWalkedCache =
|
||
|
hasWalkedCache ? hasWalkedCache.copy() : new HasWalkedCache();
|
||
|
}
|
||
|
processPatterns(target, patterns) {
|
||
|
this.patterns = patterns;
|
||
|
const processingSet = patterns.map(p => [target, p]);
|
||
|
// map of paths to the magic-starting subwalks they need to walk
|
||
|
// first item in patterns is the filter
|
||
|
for (let [t, pattern] of processingSet) {
|
||
|
this.hasWalkedCache.storeWalked(t, pattern);
|
||
|
const root = pattern.root();
|
||
|
const absolute = pattern.isAbsolute() && this.opts.absolute !== false;
|
||
|
// start absolute patterns at root
|
||
|
if (root) {
|
||
|
t = t.resolve(root === '/' && this.opts.root !== undefined ?
|
||
|
this.opts.root
|
||
|
: root);
|
||
|
const rest = pattern.rest();
|
||
|
if (!rest) {
|
||
|
this.matches.add(t, true, false);
|
||
|
continue;
|
||
|
}
|
||
|
else {
|
||
|
pattern = rest;
|
||
|
}
|
||
|
}
|
||
|
if (t.isENOENT())
|
||
|
continue;
|
||
|
let p;
|
||
|
let rest;
|
||
|
let changed = false;
|
||
|
while (typeof (p = pattern.pattern()) === 'string' &&
|
||
|
(rest = pattern.rest())) {
|
||
|
const c = t.resolve(p);
|
||
|
t = c;
|
||
|
pattern = rest;
|
||
|
changed = true;
|
||
|
}
|
||
|
p = pattern.pattern();
|
||
|
rest = pattern.rest();
|
||
|
if (changed) {
|
||
|
if (this.hasWalkedCache.hasWalked(t, pattern))
|
||
|
continue;
|
||
|
this.hasWalkedCache.storeWalked(t, pattern);
|
||
|
}
|
||
|
// now we have either a final string for a known entry,
|
||
|
// more strings for an unknown entry,
|
||
|
// or a pattern starting with magic, mounted on t.
|
||
|
if (typeof p === 'string') {
|
||
|
// must not be final entry, otherwise we would have
|
||
|
// concatenated it earlier.
|
||
|
const ifDir = p === '..' || p === '' || p === '.';
|
||
|
this.matches.add(t.resolve(p), absolute, ifDir);
|
||
|
continue;
|
||
|
}
|
||
|
else if (p === minimatch_1.GLOBSTAR) {
|
||
|
// if no rest, match and subwalk pattern
|
||
|
// if rest, process rest and subwalk pattern
|
||
|
// if it's a symlink, but we didn't get here by way of a
|
||
|
// globstar match (meaning it's the first time THIS globstar
|
||
|
// has traversed a symlink), then we follow it. Otherwise, stop.
|
||
|
if (!t.isSymbolicLink() ||
|
||
|
this.follow ||
|
||
|
pattern.checkFollowGlobstar()) {
|
||
|
this.subwalks.add(t, pattern);
|
||
|
}
|
||
|
const rp = rest?.pattern();
|
||
|
const rrest = rest?.rest();
|
||
|
if (!rest || ((rp === '' || rp === '.') && !rrest)) {
|
||
|
// only HAS to be a dir if it ends in **/ or **/.
|
||
|
// but ending in ** will match files as well.
|
||
|
this.matches.add(t, absolute, rp === '' || rp === '.');
|
||
|
}
|
||
|
else {
|
||
|
if (rp === '..') {
|
||
|
// this would mean you're matching **/.. at the fs root,
|
||
|
// and no thanks, I'm not gonna test that specific case.
|
||
|
/* c8 ignore start */
|
||
|
const tp = t.parent || t;
|
||
|
/* c8 ignore stop */
|
||
|
if (!rrest)
|
||
|
this.matches.add(tp, absolute, true);
|
||
|
else if (!this.hasWalkedCache.hasWalked(tp, rrest)) {
|
||
|
this.subwalks.add(tp, rrest);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (p instanceof RegExp) {
|
||
|
this.subwalks.add(t, pattern);
|
||
|
}
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
subwalkTargets() {
|
||
|
return this.subwalks.keys();
|
||
|
}
|
||
|
child() {
|
||
|
return new Processor(this.opts, this.hasWalkedCache);
|
||
|
}
|
||
|
// return a new Processor containing the subwalks for each
|
||
|
// child entry, and a set of matches, and
|
||
|
// a hasWalkedCache that's a copy of this one
|
||
|
// then we're going to call
|
||
|
filterEntries(parent, entries) {
|
||
|
const patterns = this.subwalks.get(parent);
|
||
|
// put matches and entry walks into the results processor
|
||
|
const results = this.child();
|
||
|
for (const e of entries) {
|
||
|
for (const pattern of patterns) {
|
||
|
const absolute = pattern.isAbsolute();
|
||
|
const p = pattern.pattern();
|
||
|
const rest = pattern.rest();
|
||
|
if (p === minimatch_1.GLOBSTAR) {
|
||
|
results.testGlobstar(e, pattern, rest, absolute);
|
||
|
}
|
||
|
else if (p instanceof RegExp) {
|
||
|
results.testRegExp(e, p, rest, absolute);
|
||
|
}
|
||
|
else {
|
||
|
results.testString(e, p, rest, absolute);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return results;
|
||
|
}
|
||
|
testGlobstar(e, pattern, rest, absolute) {
|
||
|
if (this.dot || !e.name.startsWith('.')) {
|
||
|
if (!pattern.hasMore()) {
|
||
|
this.matches.add(e, absolute, false);
|
||
|
}
|
||
|
if (e.canReaddir()) {
|
||
|
// if we're in follow mode or it's not a symlink, just keep
|
||
|
// testing the same pattern. If there's more after the globstar,
|
||
|
// then this symlink consumes the globstar. If not, then we can
|
||
|
// follow at most ONE symlink along the way, so we mark it, which
|
||
|
// also checks to ensure that it wasn't already marked.
|
||
|
if (this.follow || !e.isSymbolicLink()) {
|
||
|
this.subwalks.add(e, pattern);
|
||
|
}
|
||
|
else if (e.isSymbolicLink()) {
|
||
|
if (rest && pattern.checkFollowGlobstar()) {
|
||
|
this.subwalks.add(e, rest);
|
||
|
}
|
||
|
else if (pattern.markFollowGlobstar()) {
|
||
|
this.subwalks.add(e, pattern);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// if the NEXT thing matches this entry, then also add
|
||
|
// the rest.
|
||
|
if (rest) {
|
||
|
const rp = rest.pattern();
|
||
|
if (typeof rp === 'string' &&
|
||
|
// dots and empty were handled already
|
||
|
rp !== '..' &&
|
||
|
rp !== '' &&
|
||
|
rp !== '.') {
|
||
|
this.testString(e, rp, rest.rest(), absolute);
|
||
|
}
|
||
|
else if (rp === '..') {
|
||
|
/* c8 ignore start */
|
||
|
const ep = e.parent || e;
|
||
|
/* c8 ignore stop */
|
||
|
this.subwalks.add(ep, rest);
|
||
|
}
|
||
|
else if (rp instanceof RegExp) {
|
||
|
this.testRegExp(e, rp, rest.rest(), absolute);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
testRegExp(e, p, rest, absolute) {
|
||
|
if (!p.test(e.name))
|
||
|
return;
|
||
|
if (!rest) {
|
||
|
this.matches.add(e, absolute, false);
|
||
|
}
|
||
|
else {
|
||
|
this.subwalks.add(e, rest);
|
||
|
}
|
||
|
}
|
||
|
testString(e, p, rest, absolute) {
|
||
|
// should never happen?
|
||
|
if (!e.isNamed(p))
|
||
|
return;
|
||
|
if (!rest) {
|
||
|
this.matches.add(e, absolute, false);
|
||
|
}
|
||
|
else {
|
||
|
this.subwalks.add(e, rest);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exports.Processor = Processor;
|
||
|
//# sourceMappingURL=processor.js.map
|