From b7454f32c41234bc2bb7b4177c602adc072134a3 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 6 Jun 2012 13:19:44 -0600 Subject: [PATCH] updated to prototype --- lib/node-type-emitter.js | 9 +- lib/walk.js | 362 +++++++++++++++++++++++---------------- test-walk.js | 15 ++ 3 files changed, 233 insertions(+), 153 deletions(-) create mode 100644 test-walk.js diff --git a/lib/node-type-emitter.js b/lib/node-type-emitter.js index 2c28de1..9a1e01c 100644 --- a/lib/node-type-emitter.js +++ b/lib/node-type-emitter.js @@ -1,3 +1,4 @@ +/*jshint strict:true node:true es5:true onevar:true laxcomma:true laxbreak:true*/ (function () { "use strict"; @@ -43,12 +44,12 @@ // Emit events to each listener // Wait for all listeners to `next()` before continueing // (in theory this may avoid disk thrashing) - function emitSingleEvents(emitter, path, stats, next) { + function emitSingleEvents(emitter, path, stats, next, self) { var num = 1 + emitter.listeners(stats.type).length + emitter.listeners("node").length; function nextWhenReady() { num -= 1; - if (0 === num) { next(); } + if (0 === num) { next.call(self); } } emitter.emit(stats.type, path, stats, nextWhenReady); @@ -60,12 +61,12 @@ // Since the risk for disk thrashing among anything // other than files is relatively low, all types are // emitted at once, but all must complete before advancing - function emitPluralEvents(emitter, path, nodes, next) { + function emitPluralEvents(emitter, path, nodes, next, self) { var num = 1; function nextWhenReady() { num -= 1; - if (0 === num) { next(); } + if (0 === num) { next.call(self); } } fnodeTypesPlural.concat(["nodes", "errors"]).forEach(function (fnodeType) { diff --git a/lib/walk.js b/lib/walk.js index 81d4412..cf4d8e1 100644 --- a/lib/walk.js +++ b/lib/walk.js @@ -1,6 +1,7 @@ +/*jshint strict:true node:true es5:true onevar:true laxcomma:true laxbreak:true*/ // Adapted from work by jorge@jorgechamorro.com on 2010-11-25 (function () { - "use strict" + "use strict"; // Array.prototype.forEachAsync(next, item, i, collection) //require('Array.prototype.forEachAsync'); @@ -11,161 +12,224 @@ , forEachAsync = require('forEachAsync') , EventEmitter = require('events').EventEmitter , TypeEmitter = require('./node-type-emitter') + , util = require('util') ; - function create(pathname, options, sync) { - var emitter = new EventEmitter() - , q = [] - , queue = [q] - , curpath - , firstRun = true - ; - - function readdirHandler(err, files) { - var fnodeGroups = TypeEmitter.createNodeGroups() - ; - - function filesHandler(cont, file) { - var statPath - ; - - function lstatHandler(err, stat) { - stat = stat || {}; - stat.name = file; - - if (err) { - stat.error = err; - //emitter.emit('error', curpath, stat); - emitter.emit('nodeError', curpath, stat, noop); - fnodeGroups.errors.push(stat); - cont(); - } else { - TypeEmitter.sortFnodesByType(stat, fnodeGroups); - TypeEmitter.emitNodeType(emitter, curpath, stat, cont); - } - } - - emitter.emit('name', curpath, file, noop); - - statPath = curpath + '/' + file; - - if (sync) { - try { - lstatHandler(null, fs.lstatSync(statPath)); - } catch(e) { - lstatHandler(e); - } - } else { - fs.lstat(statPath, lstatHandler); - } - } - - function postFilesHandler() { - if (fnodeGroups.errors.length) { - emitter.emit('errors', curpath, fnodeGroups.errors, noop); - } - TypeEmitter.emitNodeTypeGroups(emitter, curpath, fnodeGroups, function () { - var dirs = []; - fnodeGroups.directories.forEach(function (stat) { - dirs.push(stat.name); - }); - dirs.forEach(fullPath); - queue.push(q = dirs); - next(); - }); - } - - function readFiles() { - if (!files || 0 == files.length) { - return next(); - } - - // TODO could allow user to selectively stat - // and don't stat if there are no stat listeners - emitter.emit('names', curpath, files, noop); - - if (sync) { - files.forEach(function (items) { - filesHandler(noop, items); - }); - postFilesHandler(); - } else { - forEachAsync(files, filesHandler).then(postFilesHandler); - } - } - - if (!err) { - readFiles(); - return; - } - - if (!firstRun) { - emitter.emit('directoryError', curpath, { error: err }, noop); - readFiles(); - return; - } - - firstRun = false; - fs.lstat(curpath, function (e, stat) { - - if (stat) { - files = [curpath.replace(/.*\//, '')]; - curpath = curpath.replace(files[0], ''); - } - - readFiles(); - }); - } - - function walkSync() { - var err, files; - - try { - files = fs.readdirSync(curpath); - } catch(e) { - err = e; - } - - readdirHandler(err, files); - } - - function walk() { - fs.readdir(curpath, readdirHandler); - } - - function next() { - if (q.length) { - curpath = q.pop(); - return walk(); - } - if (queue.length -= 1) { - q = queue[queue.length - 1]; - return next(); - } - emitter.emit('end'); - } - - function fullPath(v, i, o) { - o[i] = [curpath, '/', v].join(''); - } - - curpath = pathname; - - if (sync) { - walk = walkSync; - process.nextTick(walk); - } else { - walk(); - } - - return emitter; + function appendToDirs(stat) { + /*jshint validthis:true*/ + 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 + ; + + me._wsync = true; + me._wq = []; + me._wqueue = [me._wq]; + me._wcurpath = undefined; + me._wfistrun = true; + me._wcurpath = pathname; + + if (me._wsync) { + me._wWalk = me._wWalkSync; + } else { + me._wWalk = me._wWalkAsync; + } + + // TODO just one little anony won't hurt... + process.nextTick(function () { + 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); + 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 + '/' + file; + + if (!me._wsync) { + // TODO how to remove this anony? + fs.lstat(statPath, function (err, stat) { + me._wLstatHandler(err, stat); + }); + return; + } + + try { + me._wLstatHandler(null, fs.lstatSync(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) { + 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 + ; + + me._wfnodegroups = fnodeGroups; + me._wcurfiles = files; + + + if (!err) { + me._wReadFiles(); + return; + } + + if (!me._wfistrun) { + me.emit('directoryError', me._wcurpath, { error: err }, noop); + me._wReadFiles(); + return; + } + + me._wfistrun = false; + // TODO how to remove this anony? + fs.lstat(me._wcurpath, function (e, stat) { + + if (stat) { + files = [me._wcurpath.replace(/.*\//, '')]; + me._wcurpath = me._wcurpath.replace(files[0], ''); + } + + me._wReadFiles(); + }); + }; + Walker.prototype._wWalkSync = function () { + var err + , files + , me = this + ; + + try { + files = fs.readdirSync(me._wcurpath); + } catch(e) { + err = e; + } + + me._wReaddirHandler(err, files); + }; + Walker.prototype._wWalkAsync = function () { + 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(); + me._wWalk(); + return; + } + me._wqueue.length -= 1; + if (me._wqueue.length) { + me._wq = me._wqueue[me._wqueue.length - 1]; + return this._wNext(); + } + me.emit('end'); + }; + Walker.prototype._wJoinPath = function (v, i, o) { + var me = this + ; + + o[i] = [me._wcurpath, '/', v].join(''); + }; + Walker.prototype.pause = function () { + this._paused = true; + }; + Walker.prototype.resume = function () { + this._paused = false; + this._wNext(); + }; + exports.walk = function (path, opts) { - return create(path, opts, false); + return new Walker(path, opts, false); }; exports.walkSync = function (path, opts) { - return create(path, opts, true); + return new Walker(path, opts, true); }; }()); diff --git a/test-walk.js b/test-walk.js new file mode 100644 index 0000000..43987d8 --- /dev/null +++ b/test-walk.js @@ -0,0 +1,15 @@ +/*jshint strict:true node:true es5:true onevar:true laxcomma:true laxbreak:true*/ +(function () { + "use strict"; + + var walk = require('./lib/walk').walk + , walker + ; + + walker = walk('./'); + walker.on('file', function (root, stat, next) { + console.log(root, stat.name); + next(); + }); + +}());