533 lines
18 KiB
JavaScript
533 lines
18 KiB
JavaScript
|
/* wrench.js
|
||
|
*
|
||
|
* A collection of various utility functions I've found myself in need of
|
||
|
* for use with Node.js (http://nodejs.org/). This includes things like:
|
||
|
*
|
||
|
* - Recursively deleting directories in Node.js (Sync, not Async)
|
||
|
* - Recursively copying directories in Node.js (Sync, not Async)
|
||
|
* - Recursively chmoding a directory structure from Node.js (Sync, not Async)
|
||
|
* - Other things that I'll add here as time goes on. Shhhh...
|
||
|
*
|
||
|
* ~ Ryan McGrath (ryan [at] venodesigns.net)
|
||
|
*/
|
||
|
|
||
|
var fs = require("fs"),
|
||
|
_path = require("path"),
|
||
|
isWindows = !!process.platform.match(/^win/);
|
||
|
|
||
|
/* wrench.readdirSyncRecursive("directory_path");
|
||
|
*
|
||
|
* Recursively dives through directories and read the contents of all the
|
||
|
* children directories.
|
||
|
*/
|
||
|
exports.readdirSyncRecursive = function(baseDir) {
|
||
|
baseDir = baseDir.replace(/\/$/, '');
|
||
|
|
||
|
var readdirSyncRecursive = function(baseDir) {
|
||
|
var files = [],
|
||
|
curFiles,
|
||
|
nextDirs,
|
||
|
isDir = function(fname){
|
||
|
return fs.existsSync(_path.join(baseDir, fname)) ? fs.statSync( _path.join(baseDir, fname) ).isDirectory() : false;
|
||
|
},
|
||
|
prependBaseDir = function(fname){
|
||
|
return _path.join(baseDir, fname);
|
||
|
};
|
||
|
|
||
|
curFiles = fs.readdirSync(baseDir);
|
||
|
nextDirs = curFiles.filter(isDir);
|
||
|
curFiles = curFiles.map(prependBaseDir);
|
||
|
|
||
|
files = files.concat( curFiles );
|
||
|
|
||
|
while (nextDirs.length) {
|
||
|
files = files.concat( readdirSyncRecursive( _path.join(baseDir, nextDirs.shift()) ) );
|
||
|
}
|
||
|
|
||
|
return files;
|
||
|
};
|
||
|
|
||
|
// convert absolute paths to relative
|
||
|
var fileList = readdirSyncRecursive(baseDir).map(function(val){
|
||
|
return _path.relative(baseDir, val);
|
||
|
});
|
||
|
|
||
|
return fileList;
|
||
|
};
|
||
|
|
||
|
/* wrench.readdirRecursive("directory_path", function(error, files) {});
|
||
|
*
|
||
|
* Recursively dives through directories and read the contents of all the
|
||
|
* children directories.
|
||
|
*
|
||
|
* Asynchronous, so returns results/error in callback.
|
||
|
* Callback receives the of files in currently recursed directory.
|
||
|
* When no more directories are left, callback is called with null for all arguments.
|
||
|
*
|
||
|
*/
|
||
|
exports.readdirRecursive = function(baseDir, fn) {
|
||
|
baseDir = baseDir.replace(/\/$/, '');
|
||
|
|
||
|
var waitCount = 0;
|
||
|
|
||
|
function readdirRecursive(curDir) {
|
||
|
var prependcurDir = function(fname){
|
||
|
return _path.join(curDir, fname);
|
||
|
};
|
||
|
|
||
|
waitCount++;
|
||
|
fs.readdir(curDir, function(e, curFiles) {
|
||
|
if (e) {
|
||
|
fn(e);
|
||
|
return;
|
||
|
}
|
||
|
waitCount--;
|
||
|
|
||
|
curFiles = curFiles.map(prependcurDir);
|
||
|
|
||
|
curFiles.forEach(function(it) {
|
||
|
waitCount++;
|
||
|
|
||
|
fs.stat(it, function(e, stat) {
|
||
|
waitCount--;
|
||
|
|
||
|
if (e) {
|
||
|
fn(e);
|
||
|
} else {
|
||
|
if (stat.isDirectory()) {
|
||
|
readdirRecursive(it);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (waitCount == 0) {
|
||
|
fn(null, null);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
fn(null, curFiles.map(function(val) {
|
||
|
// convert absolute paths to relative
|
||
|
return _path.relative(baseDir, val);
|
||
|
}));
|
||
|
|
||
|
if (waitCount == 0) {
|
||
|
fn(null, null);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
readdirRecursive(baseDir);
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/* wrench.rmdirSyncRecursive("directory_path", failSilent);
|
||
|
*
|
||
|
* Recursively dives through directories and obliterates everything about it. This is a
|
||
|
* Sync-function, which blocks things until it's done. No idea why anybody would want an
|
||
|
* Asynchronous version. :\
|
||
|
*/
|
||
|
exports.rmdirSyncRecursive = function(path, failSilent) {
|
||
|
var files;
|
||
|
|
||
|
try {
|
||
|
files = fs.readdirSync(path);
|
||
|
} catch (err) {
|
||
|
|
||
|
if(failSilent) return;
|
||
|
throw new Error(err.message);
|
||
|
}
|
||
|
|
||
|
/* Loop through and delete everything in the sub-tree after checking it */
|
||
|
for(var i = 0; i < files.length; i++) {
|
||
|
var file = _path.join(path, files[i]);
|
||
|
var currFile = fs.lstatSync(file);
|
||
|
|
||
|
if(currFile.isDirectory()) {
|
||
|
// Recursive function back to the beginning
|
||
|
exports.rmdirSyncRecursive(file);
|
||
|
} else if(currFile.isSymbolicLink()) {
|
||
|
// Unlink symlinks
|
||
|
if (isWindows) {
|
||
|
fs.chmodSync(file, 666) // Windows needs this unless joyent/node#3006 is resolved..
|
||
|
}
|
||
|
|
||
|
fs.unlinkSync(file);
|
||
|
} else {
|
||
|
// Assume it's a file - perhaps a try/catch belongs here?
|
||
|
if (isWindows) {
|
||
|
fs.chmodSync(file, 666) // Windows needs this unless joyent/node#3006 is resolved..
|
||
|
}
|
||
|
|
||
|
fs.unlinkSync(file);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Now that we know everything in the sub-tree has been deleted, we can delete the main
|
||
|
directory. Huzzah for the shopkeep. */
|
||
|
return fs.rmdirSync(path);
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
function isFileIncluded(opts, dir, filename) {
|
||
|
|
||
|
function isMatch(filter) {
|
||
|
if (typeof filter === 'function') {
|
||
|
return filter(filename, dir) === true;
|
||
|
}
|
||
|
else {
|
||
|
// Maintain backwards compatibility and use just the filename
|
||
|
return filename.match(filter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (opts.include || opts.exclude) {
|
||
|
if (opts.exclude) {
|
||
|
if (isMatch(opts.exclude)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (opts.include) {
|
||
|
if (isMatch(opts.include)) {
|
||
|
return true;
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
else if (opts.filter) {
|
||
|
var filter = opts.filter;
|
||
|
|
||
|
if (!opts.whitelist) {
|
||
|
// if !opts.whitelist is false every file or directory
|
||
|
// which does match opts.filter will be ignored
|
||
|
return isMatch(filter) ? false : true;
|
||
|
} else {
|
||
|
// if opts.whitelist is true every file or directory
|
||
|
// which doesn't match opts.filter will be ignored
|
||
|
return !isMatch(filter) ? false : true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* wrench.copyDirSyncRecursive("directory_to_copy", "new_directory_location", opts);
|
||
|
*
|
||
|
* Recursively dives through a directory and moves all its files to a new location. This is a
|
||
|
* Synchronous function, which blocks things until it's done. If you need/want to do this in
|
||
|
* an Asynchronous manner, look at wrench.copyDirRecursively() below. Specify forceDelete to force directory overwrite.
|
||
|
*
|
||
|
* Note: Directories should be passed to this function without a trailing slash.
|
||
|
*/
|
||
|
exports.copyDirSyncRecursive = function(sourceDir, newDirLocation, opts) {
|
||
|
opts = opts || {};
|
||
|
|
||
|
try {
|
||
|
if(fs.statSync(newDirLocation).isDirectory()) {
|
||
|
if(opts.forceDelete) {
|
||
|
exports.rmdirSyncRecursive(newDirLocation);
|
||
|
} else if(!opts.preserveFiles) {
|
||
|
return new Error('You are trying to copy a directory onto a directory that already exists. Specify forceDelete or preserveFiles in the opts argument to specify desired behavior');
|
||
|
}
|
||
|
}
|
||
|
} catch(e) { }
|
||
|
|
||
|
/* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
|
||
|
var checkDir = fs.statSync(sourceDir);
|
||
|
try {
|
||
|
fs.mkdirSync(newDirLocation, checkDir.mode);
|
||
|
} catch (e) {
|
||
|
//if the directory already exists, that's okay
|
||
|
if (e.code !== 'EEXIST') throw e;
|
||
|
}
|
||
|
|
||
|
var files = fs.readdirSync(sourceDir);
|
||
|
var hasFilter = opts.filter || opts.include || opts.exclude;
|
||
|
var preserveFiles = opts.preserveFiles === true;
|
||
|
var preserveTimestamps = opts.preserveTimestamps === true;
|
||
|
|
||
|
for(var i = 0; i < files.length; i++) {
|
||
|
// ignores all files or directories which match the RegExp in opts.filter
|
||
|
if(typeof opts !== 'undefined') {
|
||
|
if (hasFilter) {
|
||
|
if (!isFileIncluded(opts, sourceDir, files[i])) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (opts.excludeHiddenUnix && /^\./.test(files[i])) continue;
|
||
|
}
|
||
|
|
||
|
var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));
|
||
|
|
||
|
var fCopyFile = function(srcFile, destFile) {
|
||
|
if(typeof opts !== 'undefined' && opts.preserveFiles && fs.existsSync(destFile)) return;
|
||
|
|
||
|
var contents = fs.readFileSync(srcFile);
|
||
|
fs.writeFileSync(destFile, contents);
|
||
|
var stat = fs.lstatSync(srcFile);
|
||
|
fs.chmodSync(destFile, stat.mode);
|
||
|
if (preserveTimestamps) {
|
||
|
fs.utimesSync(destFile, stat.atime, stat.mtime)
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if(currFile.isDirectory()) {
|
||
|
/* recursion this thing right on back. */
|
||
|
exports.copyDirSyncRecursive(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]), opts);
|
||
|
} else if(currFile.isSymbolicLink()) {
|
||
|
var symlinkFull = fs.readlinkSync(_path.join(sourceDir, files[i]));
|
||
|
symlinkFull = _path.resolve(fs.realpathSync(sourceDir), symlinkFull);
|
||
|
|
||
|
if (typeof opts !== 'undefined' && !opts.inflateSymlinks) {
|
||
|
fs.symlinkSync(symlinkFull, _path.join(newDirLocation, files[i]));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var tmpCurrFile = fs.lstatSync(symlinkFull);
|
||
|
if (tmpCurrFile.isDirectory()) {
|
||
|
exports.copyDirSyncRecursive(symlinkFull, _path.join(newDirLocation, files[i]), opts);
|
||
|
} else {
|
||
|
/* At this point, we've hit a file actually worth copying... so copy it on over. */
|
||
|
fCopyFile(symlinkFull, _path.join(newDirLocation, files[i]));
|
||
|
}
|
||
|
} else {
|
||
|
/* At this point, we've hit a file actually worth copying... so copy it on over. */
|
||
|
fCopyFile(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/* wrench.chmodSyncRecursive("directory", filemode);
|
||
|
*
|
||
|
* Recursively dives through a directory and chmods everything to the desired mode. This is a
|
||
|
* Synchronous function, which blocks things until it's done.
|
||
|
*
|
||
|
* Note: Directories should be passed to this function without a trailing slash.
|
||
|
*/
|
||
|
exports.chmodSyncRecursive = function(sourceDir, filemode) {
|
||
|
var files = fs.readdirSync(sourceDir);
|
||
|
|
||
|
for(var i = 0; i < files.length; i++) {
|
||
|
var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));
|
||
|
|
||
|
if(currFile.isDirectory()) {
|
||
|
/* ...and recursion this thing right on back. */
|
||
|
exports.chmodSyncRecursive(_path.join(sourceDir, files[i]), filemode);
|
||
|
} else {
|
||
|
/* At this point, we've hit a file actually worth copying... so copy it on over. */
|
||
|
fs.chmod(_path.join(sourceDir, files[i]), filemode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Finally, chmod the parent directory */
|
||
|
fs.chmod(sourceDir, filemode);
|
||
|
};
|
||
|
|
||
|
|
||
|
/* wrench.chownSyncRecursive("directory", uid, gid);
|
||
|
*
|
||
|
* Recursively dives through a directory and chowns everything to the desired user and group. This is a
|
||
|
* Synchronous function, which blocks things until it's done.
|
||
|
*
|
||
|
* Note: Directories should be passed to this function without a trailing slash.
|
||
|
*/
|
||
|
exports.chownSyncRecursive = function(sourceDir, uid, gid) {
|
||
|
var files = fs.readdirSync(sourceDir);
|
||
|
|
||
|
for(var i = 0; i < files.length; i++) {
|
||
|
var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));
|
||
|
|
||
|
if(currFile.isDirectory()) {
|
||
|
/* ...and recursion this thing right on back. */
|
||
|
exports.chownSyncRecursive(_path.join(sourceDir, files[i]), uid, gid);
|
||
|
} else {
|
||
|
/* At this point, we've hit a file actually worth chowning... so own it. */
|
||
|
fs.chownSync(_path.join(sourceDir, files[i]), uid, gid);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Finally, chown the parent directory */
|
||
|
fs.chownSync(sourceDir, uid, gid);
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
/* wrench.rmdirRecursive("directory_path", callback);
|
||
|
*
|
||
|
* Recursively dives through directories and obliterates everything about it.
|
||
|
*/
|
||
|
exports.rmdirRecursive = function rmdirRecursive(dir, failSilent, clbk){
|
||
|
if(clbk === null || typeof clbk == 'undefined')
|
||
|
clbk = function(err) {};
|
||
|
|
||
|
fs.readdir(dir, function(err, files) {
|
||
|
if(err && typeof failSilent === 'boolean' && !failSilent)
|
||
|
return clbk(err);
|
||
|
|
||
|
if(typeof failSilent === 'function')
|
||
|
clbk = failSilent;
|
||
|
|
||
|
(function rmFile(err){
|
||
|
if (err) return clbk(err);
|
||
|
|
||
|
var filename = files.shift();
|
||
|
if (filename === null || typeof filename == 'undefined')
|
||
|
return fs.rmdir(dir, clbk);
|
||
|
|
||
|
var file = dir+'/'+filename;
|
||
|
fs.lstat(file, function(err, stat){
|
||
|
if (err) return clbk(err);
|
||
|
if (stat.isDirectory())
|
||
|
rmdirRecursive(file, rmFile);
|
||
|
else
|
||
|
fs.unlink(file, rmFile);
|
||
|
});
|
||
|
})();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/* wrench.copyDirRecursive("directory_to_copy", "new_location", {forceDelete: bool}, callback);
|
||
|
*
|
||
|
* Recursively dives through a directory and moves all its files to a new
|
||
|
* location. Specify forceDelete to force directory overwrite.
|
||
|
*
|
||
|
* Note: Directories should be passed to this function without a trailing slash.
|
||
|
*/
|
||
|
exports.copyDirRecursive = function copyDirRecursive(srcDir, newDir, opts, clbk) {
|
||
|
var originalArguments = Array.prototype.slice.apply(arguments);
|
||
|
srcDir = _path.normalize(srcDir);
|
||
|
newDir = _path.normalize(newDir);
|
||
|
|
||
|
fs.stat(newDir, function(err, newDirStat) {
|
||
|
if(!err) {
|
||
|
if(typeof opts !== 'undefined' && typeof opts !== 'function' && opts.forceDelete)
|
||
|
return exports.rmdirRecursive(newDir, function(err) {
|
||
|
copyDirRecursive.apply(this, originalArguments);
|
||
|
});
|
||
|
else if(typeof opts !== 'undefined' && typeof opts !== 'function' && !opts.preserveFiles) {
|
||
|
return clbk(new Error('You are trying to copy a directory onto a directory that already exists. Specify forceDelete or preserveFiles in the opts argument to specify desired behavior'));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(typeof opts === 'function')
|
||
|
clbk = opts;
|
||
|
|
||
|
fs.stat(srcDir, function(err, srcDirStat){
|
||
|
if (err) return clbk(err);
|
||
|
fs.mkdir(newDir, srcDirStat.mode, function(err){
|
||
|
if (err) return clbk(err);
|
||
|
fs.readdir(srcDir, function(err, files){
|
||
|
if (err) return clbk(err);
|
||
|
(function copyFiles(err){
|
||
|
if (err) return clbk(err);
|
||
|
|
||
|
var filename = files.shift();
|
||
|
if (filename === null || typeof filename == 'undefined')
|
||
|
return clbk(null);
|
||
|
|
||
|
var file = srcDir+'/'+filename,
|
||
|
newFile = newDir+'/'+filename;
|
||
|
|
||
|
fs.stat(file, function(err, fileStat){
|
||
|
if (err) return clbk(err);
|
||
|
if (fileStat.isDirectory())
|
||
|
copyDirRecursive(file, newFile, copyFiles, clbk);
|
||
|
else if (fileStat.isSymbolicLink())
|
||
|
fs.readlink(file, function(err, link){
|
||
|
if (err) return clbk(err);
|
||
|
fs.symlink(link, newFile, copyFiles);
|
||
|
});
|
||
|
else
|
||
|
fs.readFile(file, function(err, data){
|
||
|
if (err) return clbk(err);
|
||
|
fs.writeFile(newFile, data, copyFiles);
|
||
|
});
|
||
|
});
|
||
|
})();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var mkdirSyncRecursive = function(path, mode) {
|
||
|
var self = this;
|
||
|
path = _path.normalize(path)
|
||
|
|
||
|
try {
|
||
|
fs.mkdirSync(path, mode);
|
||
|
} catch(err) {
|
||
|
if(err.code == "ENOENT") {
|
||
|
var slashIdx = path.lastIndexOf(_path.sep);
|
||
|
|
||
|
if(slashIdx > 0) {
|
||
|
var parentPath = path.substring(0, slashIdx);
|
||
|
mkdirSyncRecursive(parentPath, mode);
|
||
|
mkdirSyncRecursive(path, mode);
|
||
|
} else {
|
||
|
throw err;
|
||
|
}
|
||
|
} else if(err.code == "EEXIST") {
|
||
|
return;
|
||
|
} else {
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
exports.mkdirSyncRecursive = mkdirSyncRecursive;
|
||
|
|
||
|
exports.LineReader = function(filename, bufferSize) {
|
||
|
this.bufferSize = bufferSize || 8192;
|
||
|
this.buffer = "";
|
||
|
this.fd = fs.openSync(filename, "r");
|
||
|
this.currentPosition = 0;
|
||
|
};
|
||
|
|
||
|
exports.LineReader.prototype = {
|
||
|
close: function() {
|
||
|
return fs.closeSync(this.fd);
|
||
|
},
|
||
|
|
||
|
getBufferAndSetCurrentPosition: function(position) {
|
||
|
var res = fs.readSync(this.fd, this.bufferSize, position, "ascii");
|
||
|
|
||
|
this.buffer += res[0];
|
||
|
if(res[1] === 0) {
|
||
|
this.currentPosition = -1;
|
||
|
} else {
|
||
|
this.currentPosition = position + res[1];
|
||
|
}
|
||
|
|
||
|
return this.currentPosition;
|
||
|
},
|
||
|
|
||
|
hasNextLine: function() {
|
||
|
while(this.buffer.indexOf('\n') === -1) {
|
||
|
this.getBufferAndSetCurrentPosition(this.currentPosition);
|
||
|
if(this.currentPosition === -1) return false;
|
||
|
}
|
||
|
|
||
|
if(this.buffer.indexOf("\n") > -1 || this.buffer.length !== 0) return true;
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
getNextLine: function() {
|
||
|
var lineEnd = this.buffer.indexOf("\n"),
|
||
|
result = this.buffer.substring(0, lineEnd != -1 ? lineEnd : this.buffer.length);
|
||
|
|
||
|
this.buffer = this.buffer.substring(result.length + 1, this.buffer.length);
|
||
|
return result;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// vim: et ts=4 sw=4
|