added synchronous version

This commit is contained in:
AJ ONeal 2011-05-02 21:11:03 -06:00
parent 6b66729f5d
commit 8d4dc9804a
5 changed files with 258 additions and 61 deletions

View File

@ -9,6 +9,7 @@ This is somewhat of a port python's `os.walk`, but using Node.JS conventions.
* Asynchronous * Asynchronous
* Chronological (optionally) * Chronological (optionally)
* Built-in flow-control * Built-in flow-control
* includes Synchronous version (same API as Asynchronous)
As few file descriptors are opened at a time as possible. As few file descriptors are opened at a time as possible.
This is particularly well suited for single hard disks which are not flash or solid state. This is particularly well suited for single hard disks which are not flash or solid state.
@ -21,6 +22,10 @@ Installation
Usage Usage
==== ====
Both Asynchronous and Synchronous versions are provided.
The Synchronous version still uses callbacks, so it is safe to use with other Asynchronous functions and will still work as expected.
var walk = require('walk'), var walk = require('walk'),
fs = require('fs'), fs = require('fs'),
options, options,
@ -30,7 +35,10 @@ Usage
followLinks: false, followLinks: false,
}; };
walker = walk("/tmp", options); walker = walk.walk("/tmp", options);
// OR
// walker = walk.walkSync("/tmp", options);
walker.on("names", function (root, nodeNamesArray) { walker.on("names", function (root, nodeNamesArray) {
nodeNames.sort(function (a, b) { nodeNames.sort(function (a, b) {

92
lib/walk-async-only.js Normal file
View File

@ -0,0 +1,92 @@
(function () {
"use strict"
// Array.prototype.forEachAsync(next, item, i, collection)
require('futures/forEachAsync');
function noop() {}
var fs = require('fs'),
EventEmitter = require('events').EventEmitter,
TypeEmitter = require('./node-type-emitter');
// 2010-11-25 jorge@jorgechamorro.com
function create(pathname, cb) {
var emitter = new EventEmitter(),
q = [],
queue = [q],
curpath;
function walk() {
fs.readdir(curpath, function(err, files) {
if (err) {
emitter.emit('directoryError', curpath, { error: err }, noop);
//emitter.emit('error', curpath, { error: err });
}
// XXX bug was here. next() was omitted
if (!files || 0 == files.length) {
return next();
}
var fnodeGroups = TypeEmitter.createNodeGroups();
// TODO could allow user to selectively stat
// and don't stat if there are no stat listeners
emitter.emit('names', curpath, files, noop);
files.forEachAsync(function (cont, file) {
emitter.emit('name', curpath, file, noop);
fs.lstat(curpath + '/' + file, function (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);
}
});
}).then(function () {
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 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;
walk();
return emitter;
}
module.exports = create;
}());

View File

@ -1,3 +1,4 @@
// Adapted from work by jorge@jorgechamorro.com on 2010-11-25
(function () { (function () {
"use strict" "use strict"
@ -10,32 +11,21 @@
EventEmitter = require('events').EventEmitter, EventEmitter = require('events').EventEmitter,
TypeEmitter = require('./node-type-emitter'); TypeEmitter = require('./node-type-emitter');
// 2010-11-25 jorge@jorgechamorro.com function create(pathname, options, sync) {
function create(pathname, cb) { var emitter = new EventEmitter()
var emitter = new EventEmitter(), , q = []
q = [], , queue = [q]
queue = [q], , curpath;
curpath;
function walk() {
fs.readdir(curpath, function(err, files) {
if (err) {
emitter.emit('directoryError', curpath, { error: err }, noop);
//emitter.emit('error', curpath, { error: err });
}
// XXX bug was here. next() was omitted
if (!files || 0 == files.length) {
return next();
}
function readdirHandler(err, files) {
var fnodeGroups = TypeEmitter.createNodeGroups(); var fnodeGroups = TypeEmitter.createNodeGroups();
// TODO could allow user to selectively stat function filesHandler(cont, file) {
// and don't stat if there are no stat listeners var statPath;
emitter.emit('names', curpath, files, noop);
files.forEachAsync(function (cont, file) {
emitter.emit('name', curpath, file, noop); emitter.emit('name', curpath, file, noop);
fs.lstat(curpath + '/' + file, function (err, stat) {
function lstatHandler(err, stat) {
stat = stat || {}; stat = stat || {};
stat.name = file; stat.name = file;
if (err) { if (err) {
@ -48,8 +38,21 @@
TypeEmitter.sortFnodesByType(stat, fnodeGroups); TypeEmitter.sortFnodesByType(stat, fnodeGroups);
TypeEmitter.emitNodeType(emitter, curpath, stat, cont); TypeEmitter.emitNodeType(emitter, curpath, stat, cont);
} }
}); }
}).then(function () {
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) { if (fnodeGroups.errors.length) {
emitter.emit('errors', curpath, fnodeGroups.errors, noop); emitter.emit('errors', curpath, fnodeGroups.errors, noop);
} }
@ -62,8 +65,50 @@
queue.push(q = dirs); queue.push(q = dirs);
next(); next();
}); });
}
if (err) {
emitter.emit('directoryError', curpath, { error: err }, noop);
//emitter.emit('error', curpath, { error: err });
}
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 {
files.forEachAsync(filesHandler).then(postFilesHandler);
}
}
function walkSync() {
var err, files;
try {
files = fs.readdirSync(curpath);
} catch(e) {
err = e;
}
readdirHandler(err, files);
}
function walk() {
if (sync) {
walkSync();
return;
}
fs.readdir(curpath, readdirHandler);
} }
function next() { function next() {
@ -72,21 +117,32 @@
return walk(); return walk();
} }
if (queue.length -= 1) { if (queue.length -= 1) {
q = queue[queue.length-1]; q = queue[queue.length - 1];
return next(); return next();
} }
emitter.emit('end'); emitter.emit('end');
} }
function fullPath(v,i,o) { function fullPath(v, i, o) {
o[i]= [curpath, '/', v].join(''); o[i] = [curpath, '/', v].join('');
} }
curpath = pathname; curpath = pathname;
if (sync) {
process.nextTick(walk);
} else {
walk(); walk();
}
return emitter; return emitter;
} }
module.exports = create; exports.walk = function (path, opts) {
return create(path, opts, false);
};
exports.walkSync = function (path, opts) {
return create(path, opts, true);
};
}()); }());

40
testSync.js Normal file
View File

@ -0,0 +1,40 @@
(function () {
"use strict";
var walk = require('./lib/walk')
, _ = require('underscore')
, fs = require('fs')
, sync = false
, walker;
console.log(walk);
walker = walk.walk(".");
walker.on("directory", function (root, dirStatsArray, next) {
// dirStatsArray is an array of `stat` objects with the additional attributes
// * type
// * error
// * name
//console.log(_.pluck(dirStatsArray, 'name'));
console.log(root + '/' + dirStatsArray.name);
next();
});
walker.on("file", function (root, fileStats, next) {
console.log(root + '/' + fileStats.name);
next();
});
walker.on("errors", function (root, nodeStatsArray, next) {
//console.log(nodeStatsArray);
next();
});
walker.on("end", function () {
console.log("all done");
});
}());

View File

@ -1,5 +1,6 @@
mkdir -p walk-test/dir1 mkdir -p walk-test/dir1
touch walk-test/file-0
touch walk-test/file-1 touch walk-test/file-1
touch walk-test/file-2
touch walk-test/dir1/file-1
touch walk-test/dir1/file-2 touch walk-test/dir1/file-2
touch walk-test/dir1/file-3
echo "4 files and 2 directories (including '.')"