// Adapted from work by jorge@jorgechamorro.com on 2010-11-25 (function () { "use strict"; function noop() {} var fs = require('fs') , forEachAsync = require('foreachasync').forEachAsync , EventEmitter = require('events').EventEmitter , TypeEmitter = require('./node-type-emitter') , util = require('util') , path = require('path') ; function appendToDirs(stat) { /*jshint validthis:true*/ if(stat.flag && stat.flag === NO_DESCEND) { return; } this.push(stat.name); } function wFilesHandlerWrapper(items) { /*jshint validthis:true*/ this._wFilesHandler(noop, items); } function Walker(pathname, options, sync) { EventEmitter.call(this); var me = this ; options = options || {}; me._wStat = options.followLinks && 'stat' || 'lstat'; me._wStatSync = me._wStat + 'Sync'; me._wsync = sync; me._wq = []; me._wqueue = [me._wq]; me._wcurpath = undefined; me._wfilters = options.filters || []; me._wfirstrun = true; me._wcurpath = pathname; if (me._wsync) { //console.log('_walkSync'); me._wWalk = me._wWalkSync; } else { //console.log('_walkASync'); me._wWalk = me._wWalkAsync; } options.listeners = options.listeners || {}; Object.keys(options.listeners).forEach(function (event) { var callbacks = options.listeners[event] ; if ('function' === typeof callbacks) { callbacks = [callbacks]; } callbacks.forEach(function (callback) { me.on(event, callback); }); }); me._wWalk(); } // Inherits must come before prototype additions util.inherits(Walker, EventEmitter); Walker.prototype._wLstatHandler = function (err, stat) { var me = this ; stat = stat || {}; stat.name = me._wcurfile; if (err) { stat.error = err; //me.emit('error', curpath, stat); // TODO v3.0 (don't noop the next if there are listeners) me.emit('nodeError', me._wcurpath, stat, noop); me._wfnodegroups.errors.push(stat); me._wCurFileCallback(); } else { TypeEmitter.sortFnodesByType(stat, me._wfnodegroups); // NOTE: wCurFileCallback doesn't need thisness, so this is okay TypeEmitter.emitNodeType(me, me._wcurpath, stat, me._wCurFileCallback, me); } }; Walker.prototype._wFilesHandler = function (cont, file) { var statPath , me = this ; me._wcurfile = file; me._wCurFileCallback = cont; me.emit('name', me._wcurpath, file, noop); statPath = me._wcurpath + path.sep + file; if (!me._wsync) { // TODO how to remove this anony? fs[me._wStat](statPath, function (err, stat) { me._wLstatHandler(err, stat); }); return; } try { me._wLstatHandler(null, fs[me._wStatSync](statPath)); } catch(e) { me._wLstatHandler(e); } }; Walker.prototype._wOnEmitDone = function () { var me = this , dirs = [] ; me._wfnodegroups.directories.forEach(appendToDirs, dirs); dirs.forEach(me._wJoinPath, me); me._wqueue.push(me._wq = dirs); me._wNext(); }; Walker.prototype._wPostFilesHandler = function () { var me = this ; if (me._wfnodegroups.errors.length) { // TODO v3.0 (don't noop the next) // .errors is an array of stats with { name: name, error: error } me.emit('errors', me._wcurpath, me._wfnodegroups.errors, noop); } // XXX emitNodeTypes still needs refactor TypeEmitter.emitNodeTypeGroups(me, me._wcurpath, me._wfnodegroups, me._wOnEmitDone, me); }; Walker.prototype._wReadFiles = function () { var me = this ; if (!me._wcurfiles || 0 === me._wcurfiles.length) { return me._wNext(); } // TODO could allow user to selectively stat // and don't stat if there are no stat listeners me.emit('names', me._wcurpath, me._wcurfiles, noop); if (me._wsync) { me._wcurfiles.forEach(wFilesHandlerWrapper, me); me._wPostFilesHandler(); } else { forEachAsync(me._wcurfiles, me._wFilesHandler, me).then(me._wPostFilesHandler); } }; Walker.prototype._wReaddirHandler = function (err, files) { var fnodeGroups = TypeEmitter.createNodeGroups() , me = this , parent , child ; me._wfnodegroups = fnodeGroups; me._wcurfiles = files; // no error, great if (!err) { me._wReadFiles(); return; } // TODO path.sep me._wcurpath = me._wcurpath.replace(/\/$/, ''); // error? not first run? => directory error if (!me._wfirstrun) { // TODO v3.0 (don't noop the next if there are listeners) me.emit('directoryError', me._wcurpath, { error: err }, noop); // TODO v3.0 //me.emit('directoryError', me._wcurpath.replace(/^(.*)\/.*$/, '$1'), { name: me._wcurpath.replace(/^.*\/(.*)/, '$1'), error: err }, noop); me._wReadFiles(); return; } // error? first run? => maybe a file, maybe a true error me._wfirstrun = false; // readdir failed (might be a file), try a stat on the parent parent = me._wcurpath.replace(/^(.*)\/.*$/, '$1'); fs[me._wStat](parent, function (e, stat) { if (stat) { // success // now try stat on this as a child of the parent directory child = me._wcurpath.replace(/^.*\/(.*)$/, '$1'); me._wcurfiles = [child]; me._wcurpath = parent; } else { // TODO v3.0 //me.emit('directoryError', me._wcurpath.replace(/^(.*)\/.*$/, '$1'), { name: me._wcurpath.replace(/^.*\/(.*)/, '$1'), error: err }, noop); // TODO v3.0 (don't noop the next) // the original readdir error, not the parent stat error me.emit('nodeError', me._wcurpath, { error: err }, noop); } me._wReadFiles(); }); }; Walker.prototype._wFilter = function () { var me = this , exclude ; // Stop directories that contain filter keywords // from continuing through the walk process exclude = me._wfilters.some(function (filter) { if (me._wcurpath.match(filter)) { return true; } }); return exclude; }; Walker.prototype._wWalkSync = function () { //console.log('walkSync'); var err , files , me = this ; try { files = fs.readdirSync(me._wcurpath); } catch(e) { err = e; } me._wReaddirHandler(err, files); }; Walker.prototype._wWalkAsync = function () { //console.log('walkAsync'); var me = this ; // TODO how to remove this anony? fs.readdir(me._wcurpath, function (err, files) { me._wReaddirHandler(err, files); }); }; Walker.prototype._wNext = function () { var me = this ; if (me._paused) { return; } if (me._wq.length) { me._wcurpath = me._wq.pop(); while (me._wq.length && me._wFilter()) { me._wcurpath = me._wq.pop(); } if (me._wcurpath && !me._wFilter()) { me._wWalk(); } else { me._wNext(); } return; } me._wqueue.length -= 1; if (me._wqueue.length) { me._wq = me._wqueue[me._wqueue.length - 1]; return me._wNext(); } // To not break compatibility //process.nextTick(function () { me.emit('end'); //}); }; Walker.prototype._wJoinPath = function (v, i, o) { var me = this ; o[i] = [me._wcurpath, path.sep, v].join(''); }; Walker.prototype.pause = function () { this._paused = true; }; Walker.prototype.resume = function () { this._paused = false; this._wNext(); }; exports.walk = function (path, opts) { return new Walker(path, opts, false); }; exports.walkSync = function (path, opts) { return new Walker(path, opts, true); }; }());