mirror of
				https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
				synced 2025-10-31 14:55:23 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			352 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			352 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Single-use utility classes to provide functionality to the {@link Glob}
 | |
|  * methods.
 | |
|  *
 | |
|  * @module
 | |
|  */
 | |
| import { Minipass } from 'minipass';
 | |
| import { Ignore } from './ignore.js';
 | |
| import { Processor } from './processor.js';
 | |
| const makeIgnore = (ignore, opts) => typeof ignore === 'string'
 | |
|     ? new Ignore([ignore], opts)
 | |
|     : Array.isArray(ignore)
 | |
|         ? new Ignore(ignore, opts)
 | |
|         : ignore;
 | |
| /**
 | |
|  * basic walking utilities that all the glob walker types use
 | |
|  */
 | |
| export class GlobUtil {
 | |
|     path;
 | |
|     patterns;
 | |
|     opts;
 | |
|     seen = new Set();
 | |
|     paused = false;
 | |
|     aborted = false;
 | |
|     #onResume = [];
 | |
|     #ignore;
 | |
|     #sep;
 | |
|     signal;
 | |
|     maxDepth;
 | |
|     constructor(patterns, path, opts) {
 | |
|         this.patterns = patterns;
 | |
|         this.path = path;
 | |
|         this.opts = opts;
 | |
|         this.#sep = !opts.posix && opts.platform === 'win32' ? '\\' : '/';
 | |
|         if (opts.ignore) {
 | |
|             this.#ignore = makeIgnore(opts.ignore, opts);
 | |
|         }
 | |
|         // 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;
 | |
|         return this.matchCheckTest(needStat ? await e.lstat() : e, ifDir);
 | |
|     }
 | |
|     matchCheckTest(e, ifDir) {
 | |
|         return e &&
 | |
|             (this.maxDepth === Infinity || e.depth() <= this.maxDepth) &&
 | |
|             (!ifDir || e.canReaddir()) &&
 | |
|             (!this.opts.nodir || !e.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;
 | |
|         return this.matchCheckTest(needStat ? e.lstatSync() : e, ifDir);
 | |
|     }
 | |
|     matchFinish(e, absolute) {
 | |
|         if (this.#ignored(e))
 | |
|             return;
 | |
|         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(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(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();
 | |
|     }
 | |
| }
 | |
| export class GlobWalker extends GlobUtil {
 | |
|     matches;
 | |
|     constructor(patterns, path, opts) {
 | |
|         super(patterns, path, opts);
 | |
|         this.matches = new Set();
 | |
|     }
 | |
|     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;
 | |
|     }
 | |
| }
 | |
| export class GlobStream extends GlobUtil {
 | |
|     results;
 | |
|     constructor(patterns, path, opts) {
 | |
|         super(patterns, path, opts);
 | |
|         this.results = new 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;
 | |
|     }
 | |
| }
 | |
| //# sourceMappingURL=walker.js.map
 |