|
'use strict'; |
|
|
|
var fs = require('fs'); |
|
var path = require('path'); |
|
var EventEmitter = require('events'); |
|
|
|
var fastq = require('fastq'); |
|
var anymatch = require('anymatch'); |
|
var Readable = require('streamx').Readable; |
|
var isGlob = require('is-glob'); |
|
var globParent = require('glob-parent'); |
|
var normalizePath = require('normalize-path'); |
|
var isNegatedGlob = require('is-negated-glob'); |
|
var toAbsoluteGlob = require('@gulpjs/to-absolute-glob'); |
|
|
|
var globErrMessage1 = 'File not found with singular glob: '; |
|
var globErrMessage2 = ' (if this was purposeful, use `allowEmpty` option)'; |
|
|
|
function isFound(glob) { |
|
|
|
|
|
|
|
return isGlob(glob); |
|
} |
|
|
|
function walkdir() { |
|
var readdirOpts = { |
|
withFileTypes: true, |
|
}; |
|
|
|
var ee = new EventEmitter(); |
|
|
|
var queue = fastq(readdir, 1); |
|
queue.drain = function () { |
|
ee.emit('end'); |
|
}; |
|
queue.error(onError); |
|
|
|
function onError(err) { |
|
if (err) { |
|
ee.emit('error', err); |
|
} |
|
} |
|
|
|
ee.pause = function () { |
|
queue.pause(); |
|
}; |
|
ee.resume = function () { |
|
queue.resume(); |
|
}; |
|
ee.end = function () { |
|
queue.kill(); |
|
}; |
|
ee.walk = function (filepath) { |
|
queue.push(filepath); |
|
}; |
|
|
|
function readdir(filepath, cb) { |
|
fs.readdir(filepath, readdirOpts, onReaddir); |
|
|
|
function onReaddir(err, dirents) { |
|
if (err) { |
|
return cb(err); |
|
} |
|
|
|
dirents.forEach(processDirent); |
|
|
|
cb(); |
|
} |
|
|
|
function processDirent(dirent) { |
|
var nextpath = path.join(filepath, dirent.name); |
|
ee.emit('path', nextpath, dirent); |
|
|
|
if (dirent.isDirectory()) { |
|
queue.push(nextpath); |
|
} |
|
} |
|
} |
|
|
|
return ee; |
|
} |
|
|
|
function validateGlobs(globs) { |
|
var hasPositiveGlob = false; |
|
|
|
globs.forEach(validateGlobs); |
|
|
|
function validateGlobs(globString, index) { |
|
if (typeof globString !== 'string') { |
|
throw new Error('Invalid glob at index ' + index); |
|
} |
|
|
|
var result = isNegatedGlob(globString); |
|
if (result.negated === false) { |
|
hasPositiveGlob = true; |
|
} |
|
} |
|
|
|
if (hasPositiveGlob === false) { |
|
throw new Error('Missing positive glob'); |
|
} |
|
} |
|
|
|
function validateOptions(opts) { |
|
if (typeof opts.cwd !== 'string') { |
|
throw new Error('The `cwd` option must be a string'); |
|
} |
|
|
|
if (typeof opts.dot !== 'boolean') { |
|
throw new Error('The `dot` option must be a boolean'); |
|
} |
|
|
|
if (typeof opts.cwdbase !== 'boolean') { |
|
throw new Error('The `cwdbase` option must be a boolean'); |
|
} |
|
|
|
if ( |
|
typeof opts.uniqueBy !== 'string' && |
|
typeof opts.uniqueBy !== 'function' |
|
) { |
|
throw new Error('The `uniqueBy` option must be a string or function'); |
|
} |
|
|
|
if (typeof opts.allowEmpty !== 'boolean') { |
|
throw new Error('The `allowEmpty` option must be a boolean'); |
|
} |
|
|
|
if (opts.base && typeof opts.base !== 'string') { |
|
throw new Error('The `base` option must be a string if specified'); |
|
} |
|
|
|
if (!Array.isArray(opts.ignore)) { |
|
throw new Error('The `ignore` option must be a string or array'); |
|
} |
|
} |
|
|
|
function uniqueBy(comparator) { |
|
var seen = new Set(); |
|
|
|
if (typeof comparator === 'string') { |
|
return isUniqueByKey; |
|
} else { |
|
return isUniqueByFunc; |
|
} |
|
|
|
function isUnique(value) { |
|
if (seen.has(value)) { |
|
return false; |
|
} else { |
|
seen.add(value); |
|
return true; |
|
} |
|
} |
|
|
|
function isUniqueByKey(obj) { |
|
return isUnique(obj[comparator]); |
|
} |
|
|
|
function isUniqueByFunc(obj) { |
|
return isUnique(comparator(obj)); |
|
} |
|
} |
|
|
|
function globStream(globs, opt) { |
|
if (!Array.isArray(globs)) { |
|
globs = [globs]; |
|
} |
|
|
|
validateGlobs(globs); |
|
|
|
var ourOpt = Object.assign( |
|
{}, |
|
{ |
|
cwd: process.cwd(), |
|
dot: false, |
|
cwdbase: false, |
|
uniqueBy: 'path', |
|
allowEmpty: false, |
|
ignore: [], |
|
}, |
|
opt |
|
); |
|
|
|
ourOpt.ignore = |
|
typeof ourOpt.ignore === 'string' ? [ourOpt.ignore] : ourOpt.ignore; |
|
|
|
validateOptions(ourOpt); |
|
|
|
ourOpt.cwd = normalizePath(path.resolve(ourOpt.cwd), true); |
|
|
|
var base = ourOpt.base; |
|
if (ourOpt.cwdbase) { |
|
base = ourOpt.cwd; |
|
} |
|
|
|
var walker = walkdir(); |
|
|
|
var stream = new Readable({ |
|
highWaterMark: ourOpt.highWaterMark, |
|
read: read, |
|
predestroy: predestroy, |
|
}); |
|
|
|
|
|
var ourGlobs = globs.map(resolveGlob); |
|
ourOpt.ignore = ourOpt.ignore.map(resolveGlob); |
|
|
|
var found = ourGlobs.map(isFound); |
|
|
|
var matcher = anymatch(ourGlobs, null, ourOpt); |
|
|
|
var isUnique = uniqueBy(ourOpt.uniqueBy); |
|
|
|
walker.on('path', onPath); |
|
walker.once('end', onEnd); |
|
walker.once('error', onError); |
|
walker.walk(ourOpt.cwd); |
|
|
|
function read(cb) { |
|
walker.resume(); |
|
cb(); |
|
} |
|
|
|
function predestroy() { |
|
walker.end(); |
|
} |
|
|
|
function resolveGlob(glob) { |
|
return toAbsoluteGlob(glob, ourOpt); |
|
} |
|
|
|
function onPath(filepath, dirent) { |
|
var matchIdx = matcher(filepath, true); |
|
|
|
|
|
if (matchIdx === -1 && dirent.isDirectory()) { |
|
matchIdx = matcher(filepath + path.sep, true); |
|
} |
|
if (matchIdx !== -1) { |
|
found[matchIdx] = true; |
|
|
|
|
|
var basePath = base || globParent(ourGlobs[matchIdx]); |
|
|
|
var obj = { |
|
cwd: ourOpt.cwd, |
|
base: basePath, |
|
|
|
path: normalizePath(filepath, true), |
|
}; |
|
|
|
var unique = isUnique(obj); |
|
if (unique) { |
|
var drained = stream.push(obj); |
|
if (!drained) { |
|
walker.pause(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function onEnd() { |
|
var destroyed = false; |
|
|
|
found.forEach(function (matchFound, idx) { |
|
if (ourOpt.allowEmpty !== true && !matchFound) { |
|
destroyed = true; |
|
var err = new Error(globErrMessage1 + ourGlobs[idx] + globErrMessage2); |
|
|
|
return stream.destroy(err); |
|
} |
|
}); |
|
|
|
if (destroyed === false) { |
|
stream.push(null); |
|
} |
|
} |
|
|
|
function onError(err) { |
|
stream.destroy(err); |
|
} |
|
|
|
return stream; |
|
} |
|
|
|
module.exports = globStream; |
|
|