|
|
'use strict';
var fs = require('graceful-fs') , path = require('path') , micromatch = require('micromatch').isMatch , toString = Object.prototype.toString ;
// Standard helpers
function isFunction (obj) { return toString.call(obj) === '[object Function]'; }
function isString (obj) { return toString.call(obj) === '[object String]'; }
function isUndefined (obj) { return obj === void 0; }
/** * Main function which ends up calling readdirRec and reads all files and directories in given root recursively. * @param { Object } opts Options to specify root (start directory), filters and recursion depth * @param { function } callback1 When callback2 is given calls back for each processed file - function (fileInfo) { ... }, * when callback2 is not given, it behaves like explained in callback2 * @param { function } callback2 Calls back once all files have been processed with an array of errors and file infos * function (err, fileInfos) { ... } */ function readdir(opts, callback1, callback2) { var stream , handleError , handleFatalError , errors = [] , readdirResult = { directories: [] , files: [] } , fileProcessed , allProcessed , realRoot , aborted = false , paused = false ;
// If no callbacks were given we will use a streaming interface
if (isUndefined(callback1)) { var api = require('./stream-api')(); stream = api.stream; callback1 = api.processEntry; callback2 = api.done; handleError = api.handleError; handleFatalError = api.handleFatalError;
stream.on('close', function () { aborted = true; }); stream.on('pause', function () { paused = true; }); stream.on('resume', function () { paused = false; }); } else { handleError = function (err) { errors.push(err); }; handleFatalError = function (err) { handleError(err); allProcessed(errors, null); }; }
if (isUndefined(opts)){ handleFatalError(new Error ( 'Need to pass at least one argument: opts! \n' + 'https://github.com/paulmillr/readdirp#options' ) ); return stream; }
opts.root = opts.root || '.'; opts.fileFilter = opts.fileFilter || function() { return true; }; opts.directoryFilter = opts.directoryFilter || function() { return true; }; opts.depth = typeof opts.depth === 'undefined' ? 999999999 : opts.depth; opts.entryType = opts.entryType || 'files';
var statfn = opts.lstat === true ? fs.lstat.bind(fs) : fs.stat.bind(fs);
if (isUndefined(callback2)) { fileProcessed = function() { }; allProcessed = callback1; } else { fileProcessed = callback1; allProcessed = callback2; }
function normalizeFilter (filter) {
if (isUndefined(filter)) return undefined;
function isNegated (filters) {
function negated(f) { return f.indexOf('!') === 0; }
var some = filters.some(negated); if (!some) { return false; } else { if (filters.every(negated)) { return true; } else { // if we detect illegal filters, bail out immediately
throw new Error( 'Cannot mix negated with non negated glob filters: ' + filters + '\n' + 'https://github.com/paulmillr/readdirp#filters' ); } } }
// Turn all filters into a function
if (isFunction(filter)) {
return filter;
} else if (isString(filter)) {
return function (entryInfo) { return micromatch(entryInfo.name, filter.trim()); };
} else if (filter && Array.isArray(filter)) {
if (filter) filter = filter.map(function (f) { return f.trim(); });
return isNegated(filter) ? // use AND to concat multiple negated filters
function (entryInfo) { return filter.every(function (f) { return micromatch(entryInfo.name, f); }); } : // use OR to concat multiple inclusive filters
function (entryInfo) { return filter.some(function (f) { return micromatch(entryInfo.name, f); }); }; } }
function processDir(currentDir, entries, callProcessed) { if (aborted) return; var total = entries.length , processed = 0 , entryInfos = [] ;
fs.realpath(currentDir, function(err, realCurrentDir) { if (aborted) return; if (err) { handleError(err); callProcessed(entryInfos); return; }
var relDir = path.relative(realRoot, realCurrentDir);
if (entries.length === 0) { callProcessed([]); } else { entries.forEach(function (entry) {
var fullPath = path.join(realCurrentDir, entry) , relPath = path.join(relDir, entry);
statfn(fullPath, function (err, stat) { if (err) { handleError(err); } else { entryInfos.push({ name : entry , path : relPath // relative to root
, fullPath : fullPath
, parentDir : relDir // relative to root
, fullParentDir : realCurrentDir
, stat : stat }); } processed++; if (processed === total) callProcessed(entryInfos); }); }); } }); }
function readdirRec(currentDir, depth, callCurrentDirProcessed) { var args = arguments; if (aborted) return; if (paused) { setImmediate(function () { readdirRec.apply(null, args); }) return; }
fs.readdir(currentDir, function (err, entries) { if (err) { handleError(err); callCurrentDirProcessed(); return; }
processDir(currentDir, entries, function(entryInfos) {
var subdirs = entryInfos .filter(function (ei) { return ei.stat.isDirectory() && opts.directoryFilter(ei); });
subdirs.forEach(function (di) { if(opts.entryType === 'directories' || opts.entryType === 'both' || opts.entryType === 'all') { fileProcessed(di); } readdirResult.directories.push(di); });
entryInfos .filter(function(ei) { var isCorrectType = opts.entryType === 'all' ? !ei.stat.isDirectory() : ei.stat.isFile() || ei.stat.isSymbolicLink(); return isCorrectType && opts.fileFilter(ei); }) .forEach(function (fi) { if(opts.entryType === 'files' || opts.entryType === 'both' || opts.entryType === 'all') { fileProcessed(fi); } readdirResult.files.push(fi); });
var pendingSubdirs = subdirs.length;
// Be done if no more subfolders exist or we reached the maximum desired depth
if(pendingSubdirs === 0 || depth === opts.depth) { callCurrentDirProcessed(); } else { // recurse into subdirs, keeping track of which ones are done
// and call back once all are processed
subdirs.forEach(function (subdir) { readdirRec(subdir.fullPath, depth + 1, function () { pendingSubdirs = pendingSubdirs - 1; if(pendingSubdirs === 0) { callCurrentDirProcessed(); } }); }); } }); }); }
// Validate and normalize filters
try { opts.fileFilter = normalizeFilter(opts.fileFilter); opts.directoryFilter = normalizeFilter(opts.directoryFilter); } catch (err) { // if we detect illegal filters, bail out immediately
handleFatalError(err); return stream; }
// If filters were valid get on with the show
fs.realpath(opts.root, function(err, res) { if (err) { handleFatalError(err); return stream; }
realRoot = res; readdirRec(opts.root, 0, function () { // All errors are collected into the errors array
if (errors.length > 0) { allProcessed(errors, readdirResult); } else { allProcessed(null, readdirResult); } }); });
return stream; }
module.exports = readdir;
|