tiny-readdir-glob
Version:
A simple promisified recursive readdir function, with support for globs.
152 lines (151 loc) • 4.73 kB
JavaScript
/* 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 };