1 var EventEmitter = require('events').EventEmitter,
3 _path = require('path'),
4 sep = _path.sep||'/';// 0.6.x
7 module.exports = walkdir;
9 walkdir.find = walkdir.walk = walkdir;
11 walkdir.sync = function(path,options,eventHandler){
12 if(typeof options == 'function') cb = options;
13 options = options || {};
15 return walkdir(path,options,eventHandler);
19 walkdir.async = function(path,options,eventHandler){
20 return new Promise((resolve,reject)=>{
21 if(typeof options == 'function') cb = options;
22 options = options || {};
24 let emitter = walkdir(path,options,eventHandler)
26 emitter.on('error',reject)
27 emitter.on('fail',(path,err)=>{
28 err.message = 'Error walking": '+path+' '+err.message
33 emitter.on('path',(path,stat)=>{
34 if(options.no_return !== true) allPaths[path] = stat;
36 emitter.on('end',()=>{
37 if(options.no_return !== true){
38 return resolve(options.return_object?allPaths:Object.keys(allPaths))
45 function walkdir(path,options,cb){
47 if(typeof options == 'function') cb = options;
49 options = options || {};
50 if(options.find_links === undefined){
51 options.find_links = true;
54 var fs = options.fs || _fs;
56 var emitter = new EventEmitter(),
58 allPaths = (options.return_object?{}:[]),
65 job = function(value) {
67 if(value < 1 && !tick) {
69 process.nextTick(function(){
71 if(jobs <= 0 && !ended) {
79 emitter.ignore = function(path){
80 if(Array.isArray(path)) dontTraverse.push.apply(dontTraverse,path)
81 else dontTraverse.push(path)
85 //mapping is stat functions to event names.
86 var statIs = [['isFile','file'],['isDirectory','directory'],['isSymbolicLink','link'],['isSocket','socket'],['isFIFO','fifo'],['isBlockDevice','blockdevice'],['isCharacterDevice','characterdevice']];
88 var statter = function (path,first,depth) {
90 var statAction = function fn(err,stat,data) {
95 // in sync mode i found that node will sometimes return a null stat and no error =(
96 // this is reproduceable in file descriptors that no longer exist from this process
97 // after a readdir on /proc/3321/task/3321/ for example. Where 3321 is this pid
100 emitter.emit('fail',path,err);
105 //if i have evented this inode already dont again.
106 var fileName = _path.basename(path);
107 var fileKey = stat.dev + '-' + stat.ino + '-' + fileName;
108 if(options.track_inodes !== false) {
109 if(inos[fileKey] && stat.ino) return;
113 if (first && stat.isDirectory()) {
114 emitter.emit('targetdirectory',path,stat,depth);
118 emitter.emit('path', path, stat, depth);
122 for(var j=0,k=statIs.length;j<k;j++) {
123 if(stat[statIs[j][0]]()) {
124 emitter.emit(statIs[j][1],path,stat,depth);
133 stat = fs[options.find_links?'lstatSync':'statSync'](path);
140 fs[options.find_links?'lstat':'stat'](path,statAction);
142 },readdir = function(path,stat,depth){
144 path = _path.resolve(path);
148 if(options.max_depth && depth >= options.max_depth){
149 emitter.emit('maxdepth',path,stat,depth);
153 if(dontTraverse.length){
154 for(var i=0;i<dontTraverse.length;++i){
155 if(dontTraverse[i] == path) {
156 dontTraverse.splice(i,1)
163 var readdirAction = function(err,files) {
166 //permissions error or invalid files
167 emitter.emit('fail',path,err);
172 // empty directory event.
173 emitter.emit('empty',path,stat,depth);
177 if(path == sep) path='';
179 var res = options.filter(path,files)
181 throw new Error('option.filter funtion must return a array of strings or a promise')
183 // support filters that return a promise
188 for(var i=0,j=files.length;i<j;i++){
189 statter(path+sep+files[i],false,(depth||0)+1);
197 for(var i=0,j=files.length;i<j;i++){
198 statter(path+sep+files[i],false,(depth||0)+1);
204 //use same pattern for sync as async api
208 files = fs.readdirSync(path);
211 readdirAction(e,files);
213 fs.readdir(path,readdirAction);
217 if (options.follow_symlinks) {
218 var linkAction = function(err,path,depth){
220 //TODO should fail event here on error?
221 statter(path,false,depth);
224 emitter.on('link',function(path,stat,depth){
229 lpath = fs.readlinkSync(path);
233 linkAction(ex,_path.resolve(_path.dirname(path),lpath),depth);
236 fs.readlink(path,function(err,lpath){
237 linkAction(err,_path.resolve(_path.dirname(path),lpath),depth);
244 emitter.on('path',cb);
248 if(!options.no_return){
249 emitter.on('path',function(path,stat){
250 if(options.return_object) allPaths[path] = stat;
251 else allPaths.push(path);
256 if (!options.no_recurse) {
257 emitter.on('directory',readdir);
259 //directory that was specified by argument.
260 emitter.once('targetdirectory',readdir);
261 //only a fail on the path specified by argument is fatal
262 emitter.once('fail',function(_path,err){
263 //if the first dir fails its a real error
265 emitter.emit('error',path,err);
273 //support stopping everything.
274 emitter.end = emitter.stop = function(){stop = 1;};
275 //support pausing everything
277 emitter.pause = function(){
280 emitter.emit = function(){
281 emitQ.push(arguments);
284 // support getting the show going again
285 emitter.resume = function(){
291 emitter.emit = EventEmitter.prototype.emit;
294 // clear ref to prevent infinite loops
297 emitter.emit.apply(emitter,q.shift());