3 var fs = require('graceful-fs')
4 , path = require('path')
5 , minimatch = require('minimatch')
6 , toString = Object.prototype.toString
7 , si = require('set-immediate-shim')
12 function isFunction (obj) {
13 return toString.call(obj) === '[object Function]';
16 function isString (obj) {
17 return toString.call(obj) === '[object String]';
20 function isRegExp (obj) {
21 return toString.call(obj) === '[object RegExp]';
24 function isUndefined (obj) {
25 return obj === void 0;
29 * Main function which ends up calling readdirRec and reads all files and directories in given root recursively.
30 * @param { Object } opts Options to specify root (start directory), filters and recursion depth
31 * @param { function } callback1 When callback2 is given calls back for each processed file - function (fileInfo) { ... },
32 * when callback2 is not given, it behaves like explained in callback2
33 * @param { function } callback2 Calls back once all files have been processed with an array of errors and file infos
34 * function (err, fileInfos) { ... }
36 function readdir(opts, callback1, callback2) {
53 // If no callbacks were given we will use a streaming interface
54 if (isUndefined(callback1)) {
55 var api = require('./stream-api')();
57 callback1 = api.processEntry;
59 handleError = api.handleError;
60 handleFatalError = api.handleFatalError;
62 stream.on('close', function () { aborted = true; });
63 stream.on('pause', function () { paused = true; });
64 stream.on('resume', function () { paused = false; });
66 handleError = function (err) { errors.push(err); };
67 handleFatalError = function (err) {
69 allProcessed(errors, null);
73 if (isUndefined(opts)){
74 handleFatalError(new Error (
75 'Need to pass at least one argument: opts! \n' +
76 'https://github.com/thlorenz/readdirp#options'
82 opts.root = opts.root || '.';
83 opts.fileFilter = opts.fileFilter || function() { return true; };
84 opts.directoryFilter = opts.directoryFilter || function() { return true; };
85 opts.depth = typeof opts.depth === 'undefined' ? 999999999 : opts.depth;
86 opts.entryType = opts.entryType || 'files';
88 var statfn = opts.lstat === true ? fs.lstat.bind(fs) : fs.stat.bind(fs);
90 if (isUndefined(callback2)) {
91 fileProcessed = function() { };
92 allProcessed = callback1;
94 fileProcessed = callback1;
95 allProcessed = callback2;
98 function normalizeFilter (filter) {
100 if (isUndefined(filter)) return undefined;
102 function isNegated (filters) {
104 function negated(f) {
105 return f.indexOf('!') === 0;
108 var some = filters.some(negated);
112 if (filters.every(negated)) {
115 // if we detect illegal filters, bail out immediately
117 'Cannot mix negated with non negated glob filters: ' + filters + '\n' +
118 'https://github.com/thlorenz/readdirp#filters'
124 // Turn all filters into a function
125 if (isFunction(filter)) {
129 } else if (isString(filter)) {
131 return function (entryInfo) {
132 return minimatch(entryInfo.name, filter.trim());
135 } else if (filter && Array.isArray(filter)) {
137 if (filter) filter = filter.map(function (f) {
141 return isNegated(filter) ?
142 // use AND to concat multiple negated filters
143 function (entryInfo) {
144 return filter.every(function (f) {
145 return minimatch(entryInfo.name, f);
149 // use OR to concat multiple inclusive filters
150 function (entryInfo) {
151 return filter.some(function (f) {
152 return minimatch(entryInfo.name, f);
158 function processDir(currentDir, entries, callProcessed) {
160 var total = entries.length
165 fs.realpath(currentDir, function(err, realCurrentDir) {
169 callProcessed(entryInfos);
173 var relDir = path.relative(realRoot, realCurrentDir);
175 if (entries.length === 0) {
178 entries.forEach(function (entry) {
180 var fullPath = path.join(realCurrentDir, entry)
181 , relPath = path.join(relDir, entry);
183 statfn(fullPath, function (err, stat) {
189 , path : relPath // relative to root
190 , fullPath : fullPath
192 , parentDir : relDir // relative to root
193 , fullParentDir : realCurrentDir
199 if (processed === total) callProcessed(entryInfos);
206 function readdirRec(currentDir, depth, callCurrentDirProcessed) {
207 var args = arguments;
211 readdirRec.apply(null, args);
216 fs.readdir(currentDir, function (err, entries) {
219 callCurrentDirProcessed();
223 processDir(currentDir, entries, function(entryInfos) {
225 var subdirs = entryInfos
226 .filter(function (ei) { return ei.stat.isDirectory() && opts.directoryFilter(ei); });
228 subdirs.forEach(function (di) {
229 if(opts.entryType === 'directories' || opts.entryType === 'both' || opts.entryType === 'all') {
232 readdirResult.directories.push(di);
236 .filter(function(ei) {
237 var isCorrectType = opts.entryType === 'all' ?
238 !ei.stat.isDirectory() : ei.stat.isFile() || ei.stat.isSymbolicLink();
239 return isCorrectType && opts.fileFilter(ei);
241 .forEach(function (fi) {
242 if(opts.entryType === 'files' || opts.entryType === 'both' || opts.entryType === 'all') {
245 readdirResult.files.push(fi);
248 var pendingSubdirs = subdirs.length;
250 // Be done if no more subfolders exist or we reached the maximum desired depth
251 if(pendingSubdirs === 0 || depth === opts.depth) {
252 callCurrentDirProcessed();
254 // recurse into subdirs, keeping track of which ones are done
255 // and call back once all are processed
256 subdirs.forEach(function (subdir) {
257 readdirRec(subdir.fullPath, depth + 1, function () {
258 pendingSubdirs = pendingSubdirs - 1;
259 if(pendingSubdirs === 0) {
260 callCurrentDirProcessed();
269 // Validate and normalize filters
271 opts.fileFilter = normalizeFilter(opts.fileFilter);
272 opts.directoryFilter = normalizeFilter(opts.directoryFilter);
274 // if we detect illegal filters, bail out immediately
275 handleFatalError(err);
279 // If filters were valid get on with the show
280 fs.realpath(opts.root, function(err, res) {
282 handleFatalError(err);
287 readdirRec(opts.root, 0, function () {
288 // All errors are collected into the errors array
289 if (errors.length > 0) {
290 allProcessed(errors, readdirResult);
292 allProcessed(null, readdirResult);
300 module.exports = readdir;