UNPKG

deep-list-dir

Version:

Recursively lists given directory, alternatively filtering results with given options

150 lines (125 loc) 4.09 kB
/*! * deep-list-dir v1.4.4 * (c) 2020-2021 Martin Rafael Gonzalez <tin@devtin.io> * MIT */ import fs from 'fs'; import path from 'path'; import util from 'util'; import castArray from 'lodash/castArray'; import each from 'lodash/each'; import flattenDeep from 'lodash/flattenDeep'; import { Minimatch } from 'minimatch'; const readdirAsync = util.promisify(fs.readdir); const lstat = util.promisify(fs.lstat); const MinimatchOptions = { matchBase: true }; /** * Creates patterns * @param {String[]|RegExp[]|RegExp|String} patterns * @param {Object} [minimatchOptions] */ function parsePatterns (patterns, minimatchOptions) { return castArray(patterns).filter(Boolean).map(pattern => { return typeof pattern === 'string' ? new Minimatch(pattern, minimatchOptions) : pattern }) } /** * Checks if given `fullFile` should be included according to provided `patterns` * * @param patterns * @param base * @param fullFile * @return {{excluded: boolean, included: boolean}} */ function includeFile ({ patterns, base, fullFile }) { let included = !patterns.length; let excluded = false; each(patterns, pattern => { const relativeFile = path.relative(base, fullFile); const isRegex = pattern instanceof RegExp; if (isRegex) { if (!included) { included = pattern.test(relativeFile); } return } if (pattern.negate) { if (!excluded) { excluded = !pattern.match(relativeFile); return !excluded } return } if (!included) { included = pattern.match(relativeFile); } }); return { included, excluded } } /** * Deeply scans the given `directory` and returns results optionally filtering those matching given * <a href="https://github.com/isaacs/minimatch#readme" target="_blank">minimatch</a> `pattern` returning an array of * strings with absolute paths to all of the matching files. * * @param {String} directory - The directory to scan * @param {Object} options * @param {String[]|String|RegExp|RegExp[]} [options.pattern] - Minimatch pattern or RegExp * @param {String[]|String|RegExp|RegExp[]} [options.base] - Minimatch pattern or RegExp * @param {String[]|String|RegExp|RegExp[]} [options.minimatchOptions] - Additional minimatch options * @return {Promise<String[]>} Paths found */ async function deepListDir (directory, { pattern: patterns, base, mainBase, minimatchOptions = MinimatchOptions } = {}) { base = base || directory; mainBase = mainBase || base; patterns = parsePatterns(patterns, minimatchOptions); const files = (await readdirAsync(directory)).map(file => { /* eslint-disable-next-line */ return new Promise(async (resolve) => { const fullFile = path.join(directory, file); const { excluded, included } = includeFile({ patterns, fullFile, base: mainBase }); if (!excluded) { const isDirectory = (await lstat(fullFile)).isDirectory(); if (isDirectory) { return resolve(deepListDir(fullFile, { pattern: patterns, base: directory, mainBase })) } } if (!included) { return resolve() } return resolve(fullFile) }) }); return flattenDeep(await Promise.all(files)).filter(Boolean) } function deepListDirSync (directory, { pattern: patterns, base, mainBase, minimatchOptions = MinimatchOptions } = {}) { base = base || directory; mainBase = mainBase || base; patterns = parsePatterns(patterns, minimatchOptions); const files = fs.readdirSync(directory).map(file => { const fullFile = path.join(directory, file); const { excluded, included } = includeFile({ patterns, fullFile, base: mainBase }); const isDirectory = fs.lstatSync(fullFile).isDirectory(); if (!excluded && isDirectory) { return deepListDirSync(fullFile, { pattern: patterns, base: directory, mainBase }) } if (!included) { return } return fullFile }); return flattenDeep(files).filter(Boolean) } export { deepListDir, deepListDirSync };