294 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// synchronous utility for filtering entries and calculating subwalks
 | 
						|
import { GLOBSTAR } from 'minimatch';
 | 
						|
/**
 | 
						|
 * A cache of which patterns have been processed for a given Path
 | 
						|
 */
 | 
						|
export 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()]));
 | 
						|
    }
 | 
						|
}
 | 
						|
/**
 | 
						|
 * 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.
 | 
						|
 */
 | 
						|
export 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),
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
}
 | 
						|
/**
 | 
						|
 * A collection of patterns that must be processed in a subsequent step
 | 
						|
 * for a given path.
 | 
						|
 */
 | 
						|
export 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());
 | 
						|
    }
 | 
						|
}
 | 
						|
/**
 | 
						|
 * The class that processes patterns for a given path.
 | 
						|
 *
 | 
						|
 * Handles child entry filtering, and determining whether a path's
 | 
						|
 * directory contents must be read.
 | 
						|
 */
 | 
						|
export 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 === 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 === 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);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
//# sourceMappingURL=processor.js.map
 |