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,cb){ if(typeof options == 'function') cb = options; options = options || {}; options.sync = true; return walkdir(path,options,cb); }; function walkdir(path,options,cb){ if(typeof options == 'function') cb = options; options = options || {}; 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(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.lstatSync(path); } catch (e) { ex = e; } statAction(ex,stat); } else { fs.lstat(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=''; 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; } }