--- /dev/null
+var EventEmitter = require('events').EventEmitter,
+_fs = require('fs'),
+_path = require('path'),
+sep = _path.sep||'/';// 0.6.x
+
+
+module.exports = walkdir;
+
+walkdir.find = walkdir.walk = walkdir;
+
+walkdir.sync = function(path,options,eventHandler){
+ if(typeof options == 'function') cb = options;
+ options = options || {};
+ options.sync = true;
+ return walkdir(path,options,eventHandler);
+};
+
+// return promise.
+walkdir.async = function(path,options,eventHandler){
+ return new Promise((resolve,reject)=>{
+ if(typeof options == 'function') cb = options;
+ options = options || {};
+
+ let emitter = walkdir(path,options,eventHandler)
+
+ emitter.on('error',reject)
+ emitter.on('fail',(path,err)=>{
+ err.message = 'Error walking": '+path+' '+err.message
+ if(err) reject(err)
+ })
+
+ let allPaths = {}
+ emitter.on('path',(path,stat)=>{
+ if(options.no_return !== true) allPaths[path] = stat;
+ })
+ emitter.on('end',()=>{
+ if(options.no_return !== true){
+ return resolve(options.return_object?allPaths:Object.keys(allPaths))
+ }
+ resolve()
+ })
+ })
+}
+
+function walkdir(path,options,cb){
+
+ if(typeof options == 'function') cb = options;
+
+ options = options || {};
+ if(options.find_links === undefined){
+ options.find_links = true;
+ }
+
+ var fs = options.fs || _fs;
+
+ var emitter = new EventEmitter(),
+ dontTraverse = [],
+ allPaths = (options.return_object?{}:[]),
+ resolved = false,
+ inos = {},
+ stop = 0,
+ pause = null,
+ ended = 0,
+ jobs=0,
+ job = function(value) {
+ jobs += value;
+ if(value < 1 && !tick) {
+ tick = 1;
+ process.nextTick(function(){
+ tick = 0;
+ if(jobs <= 0 && !ended) {
+ ended = 1;
+ emitter.emit('end');
+ }
+ });
+ }
+ }, tick = 0;
+
+ emitter.ignore = function(path){
+ if(Array.isArray(path)) dontTraverse.push.apply(dontTraverse,path)
+ else dontTraverse.push(path)
+ return this
+ }
+
+ //mapping is stat functions to event names.
+ var statIs = [['isFile','file'],['isDirectory','directory'],['isSymbolicLink','link'],['isSocket','socket'],['isFIFO','fifo'],['isBlockDevice','blockdevice'],['isCharacterDevice','characterdevice']];
+
+ var statter = function (path,first,depth) {
+ job(1);
+ var statAction = function fn(err,stat,data) {
+
+ job(-1);
+ if(stop) return;
+
+ // in sync mode i found that node will sometimes return a null stat and no error =(
+ // this is reproduceable in file descriptors that no longer exist from this process
+ // after a readdir on /proc/3321/task/3321/ for example. Where 3321 is this pid
+ // node @ v0.6.10
+ if(err || !stat) {
+ emitter.emit('fail',path,err);
+ return;
+ }
+
+
+ //if i have evented this inode already dont again.
+ var fileName = _path.basename(path);
+ var fileKey = stat.dev + '-' + stat.ino + '-' + fileName;
+ if(options.track_inodes !== false) {
+ if(inos[fileKey] && stat.ino) return;
+ inos[fileKey] = 1;
+ }
+
+ if (first && stat.isDirectory()) {
+ emitter.emit('targetdirectory',path,stat,depth);
+ return;
+ }
+
+ emitter.emit('path', path, stat, depth);
+
+ var i,name;
+
+ for(var j=0,k=statIs.length;j<k;j++) {
+ if(stat[statIs[j][0]]()) {
+ emitter.emit(statIs[j][1],path,stat,depth);
+ break;
+ }
+ }
+ };
+
+ if(options.sync) {
+ var stat,ex;
+ try{
+ stat = fs[options.find_links?'lstatSync':'statSync'](path);
+ } catch (e) {
+ ex = e;
+ }
+
+ statAction(ex,stat);
+ } else {
+ fs[options.find_links?'lstat':'stat'](path,statAction);
+ }
+ },readdir = function(path,stat,depth){
+ if(!resolved) {
+ path = _path.resolve(path);
+ resolved = 1;
+ }
+
+ if(options.max_depth && depth >= options.max_depth){
+ emitter.emit('maxdepth',path,stat,depth);
+ return;
+ }
+
+ if(dontTraverse.length){
+ for(var i=0;i<dontTraverse.length;++i){
+ if(dontTraverse[i] == path) {
+ dontTraverse.splice(i,1)
+ return;
+ }
+ }
+ }
+
+ job(1);
+ var readdirAction = function(err,files) {
+ job(-1);
+ if (err || !files) {
+ //permissions error or invalid files
+ emitter.emit('fail',path,err);
+ return;
+ }
+
+ if(!files.length) {
+ // empty directory event.
+ emitter.emit('empty',path,stat,depth);
+ return;
+ }
+
+ if(path == sep) path='';
+ if(options.filter){
+ var res = options.filter(path,files)
+ if(!res){
+ throw new Error('option.filter funtion must return a array of strings or a promise')
+ }
+ // support filters that return a promise
+ if(res.then){
+ job(1)
+ res.then((files)=>{
+ job(-1)
+ for(var i=0,j=files.length;i<j;i++){
+ statter(path+sep+files[i],false,(depth||0)+1);
+ }
+ })
+ return;
+ }
+ //filtered files.
+ files = res
+ }
+ for(var i=0,j=files.length;i<j;i++){
+ statter(path+sep+files[i],false,(depth||0)+1);
+ }
+
+ };
+
+
+ //use same pattern for sync as async api
+ if(options.sync) {
+ var e,files;
+ try {
+ files = fs.readdirSync(path);
+ } catch (e) { }
+
+ readdirAction(e,files);
+ } else {
+ fs.readdir(path,readdirAction);
+ }
+ };
+
+ if (options.follow_symlinks) {
+ var linkAction = function(err,path,depth){
+ job(-1);
+ //TODO should fail event here on error?
+ statter(path,false,depth);
+ };
+
+ emitter.on('link',function(path,stat,depth){
+ job(1);
+ if(options.sync) {
+ var lpath,ex;
+ try {
+ lpath = fs.readlinkSync(path);
+ } catch(e) {
+ ex = e;
+ }
+ linkAction(ex,_path.resolve(_path.dirname(path),lpath),depth);
+
+ } else {
+ fs.readlink(path,function(err,lpath){
+ linkAction(err,_path.resolve(_path.dirname(path),lpath),depth);
+ });
+ }
+ });
+ }
+
+ if (cb) {
+ emitter.on('path',cb);
+ }
+
+ if (options.sync) {
+ if(!options.no_return){
+ emitter.on('path',function(path,stat){
+ if(options.return_object) allPaths[path] = stat;
+ else allPaths.push(path);
+ });
+ }
+ }
+
+ if (!options.no_recurse) {
+ emitter.on('directory',readdir);
+ }
+ //directory that was specified by argument.
+ emitter.once('targetdirectory',readdir);
+ //only a fail on the path specified by argument is fatal
+ emitter.once('fail',function(_path,err){
+ //if the first dir fails its a real error
+ if(path == _path) {
+ emitter.emit('error',path,err);
+ }
+ });
+
+ statter(path,1);
+ if (options.sync) {
+ return allPaths;
+ } else {
+ //support stopping everything.
+ emitter.end = emitter.stop = function(){stop = 1;};
+ //support pausing everything
+ var emitQ = [];
+ emitter.pause = function(){
+ job(1);
+ pause = true;
+ emitter.emit = function(){
+ emitQ.push(arguments);
+ };
+ };
+ // support getting the show going again
+ emitter.resume = function(){
+ if(!pause) return;
+ pause = false;
+ // not pending
+ job(-1);
+ //replace emit
+ emitter.emit = EventEmitter.prototype.emit;
+ // local ref
+ var q = emitQ;
+ // clear ref to prevent infinite loops
+ emitQ = [];
+ while(q.length) {
+ emitter.emit.apply(emitter,q.shift());
+ }
+ };
+
+ return emitter;
+ }
+
+}