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