UNPKG

tiny-readdir-glob

Version:

A simple promisified recursive readdir function, with support for globs.

152 lines (151 loc) 4.73 kB
/* IMPORT */ import path from 'node:path'; import zeptomatch from 'zeptomatch'; import { explodeStart, explodeEnd } from 'zeptomatch-explode'; import isStatic from 'zeptomatch-is-static'; import unescape from 'zeptomatch-unescape'; /* MAIN */ const castArray = (value) => { return Array.isArray(value) ? value : [value]; }; const globExplode = (glob) => { if (isStatic(glob)) { // Handling it as a relative path, not a glob return [[unescape(glob)], '**/*']; } else { // Handling it as an actual glob const { statics, dynamic } = explodeStart(glob); return [statics, dynamic]; } }; const globsExplode = (globs) => { const results = []; for (const glob of globs) { const [paths, pathsGlob] = globExplode(glob); if (!paths.length) { paths.push(''); } for (const path of paths) { const existing = results.find(result => result[0].includes(path)); if (existing) { if (!existing[1].includes(pathsGlob)) { existing[1].push(pathsGlob); } } else { results.push([[path], [pathsGlob]]); } } } return results; }; const globCompile = (glob) => { if (!glob || glob === '**/*') { // Trivial case return () => true; } const { flexibleStart, flexibleEnd, statics, dynamic } = explodeEnd(glob); if (dynamic === '**/*' && statics.length && !flexibleEnd) { // Optimized case return (rootPath, targetPath) => { for (let i = 0, l = statics.length; i < l; i++) { const end = statics[i]; if (!targetPath.endsWith(end)) continue; if (flexibleStart) return true; if (targetPath.length === end.length) return true; if (isPathSep(targetPath[targetPath.length - end.length - 1])) return true; } return false; }; } else { // Unoptimized case const re = zeptomatch.compile(glob); return (rootPath, targetPath) => { return re.test(path.relative(rootPath, targetPath)); }; } }; const globsCompile = (globs) => { const fns = globs.map(globCompile); return (rootPath, targetPath) => fns.some(fn => fn(rootPath, targetPath)); }; const globsPartition = (globs) => { const positives = []; const negatives = []; const bangsRe = /^!+/; if (globs.length) { for (const glob of globs) { const match = glob.match(bangsRe); if (match) { const bangsNr = match[0].length; const bucket = bangsNr % 2 === 0 ? positives : negatives; bucket.push(glob.slice(bangsNr)); } else { positives.push(glob); } } if (!positives.length) { positives.push('**'); } } return [positives, negatives]; }; const ignoreCompile = (rootPath, ignore) => { if (!ignore) return; return castArray(ignore).map(ignore => { if (!isString(ignore)) return ignore; const fn = globCompile(ignore); return (targetPath) => fn(rootPath, targetPath); }); }; const intersection = (sets) => { if (sets.length === 1) return sets[0]; const result = new Set(); for (let i = 0, l = sets.length; i < l; i++) { const set = sets[i]; for (const value of set) { result.add(value); } } return result; }; const isPathSep = (char) => { return char === '/' || char === '\\'; }; const isString = (value) => { return typeof value === 'string'; }; const uniq = (values) => { if (values.length < 2) return values; return Array.from(new Set(values)); }; const uniqFlat = (values) => { if (values.length === 1) return values[0]; return uniq(values.flat()); }; const uniqMergeConcat = (values) => { if (values.length === 1) return values[0]; const merged = {}; for (let i = 0, l = values.length; i < l; i++) { const value = values[i]; for (const key in value) { const prev = merged[key]; const next = Array.isArray(prev) ? prev.concat(value[key]) : value[key]; merged[key] = next; } } for (const key in merged) { merged[key] = uniq(merged[key]); } return merged; }; /* EXPORT */ export { castArray, globExplode, globsExplode, globCompile, globsCompile, globsPartition, ignoreCompile, intersection, isPathSep, isString, uniq, uniqFlat, uniqMergeConcat };