added synchronous version
This commit is contained in:
parent
6b66729f5d
commit
8d4dc9804a
12
README.md
12
README.md
|
@ -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) {
|
||||||
|
@ -148,4 +156,4 @@ Note that `find.js` omits the start directory
|
||||||
user 0m17.661s
|
user 0m17.661s
|
||||||
sys 0m24.008s
|
sys 0m24.008s
|
||||||
|
|
||||||
In conclusion node.js asynchronous walk is much slower than regular "find".
|
In conclusion node.js asynchronous walk is much slower than regular "find".
|
||||||
|
|
|
@ -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;
|
||||||
|
}());
|
170
lib/walk.js
170
lib/walk.js
|
@ -1,3 +1,4 @@
|
||||||
|
// Adapted from work by jorge@jorgechamorro.com on 2010-11-25
|
||||||
(function () {
|
(function () {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
|
@ -10,60 +11,104 @@
|
||||||
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 readdirHandler(err, files) {
|
||||||
|
var fnodeGroups = TypeEmitter.createNodeGroups();
|
||||||
|
|
||||||
|
function filesHandler(cont, file) {
|
||||||
|
var statPath;
|
||||||
|
|
||||||
|
emitter.emit('name', curpath, file, noop);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
function walk() {
|
||||||
fs.readdir(curpath, function(err, files) {
|
if (sync) {
|
||||||
if (err) {
|
walkSync();
|
||||||
emitter.emit('directoryError', curpath, { error: err }, noop);
|
return;
|
||||||
//emitter.emit('error', curpath, { error: err });
|
}
|
||||||
}
|
|
||||||
// XXX bug was here. next() was omitted
|
|
||||||
if (!files || 0 == files.length) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
var fnodeGroups = TypeEmitter.createNodeGroups();
|
fs.readdir(curpath, readdirHandler);
|
||||||
|
|
||||||
// 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() {
|
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;
|
||||||
walk();
|
|
||||||
|
if (sync) {
|
||||||
|
process.nextTick(walk);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
};
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -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");
|
||||||
|
});
|
||||||
|
}());
|
|
@ -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 '.')"
|
||||||
|
|
Loading…
Reference in New Issue