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