123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- '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;
|