diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cd242b1 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +test: + mocha --reporter list -u tdd + +.PHONY: test diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc7b1d8 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +Filesystem Utilities +==================== + +*Asynchronous* Filesystem utilities for Node.js. + +Requirements +------------ + +* [mocha](http://visionmedia.github.com/mocha/) (if you want to run tests) + +API +--- + +#### rm_rf(path, [callback]) + +Remove the directory on `path` recursively. + +#### mkdir_p(path, [callback]) + +Create a directory and all its parent directories. + +#### cp(src, dest, [callback]) + +Copy a file content `src` to `dest`. + +#### cp_r(src, dest, [callback]) + +Copy file contents `src` to `dest` recursively. + +#### ln_s(src, path, [callback]) + +Creates a symbolic link `path` which points to `src`. + +#### ln_sf(src, path, [callback]) + +Creates a symbolic link `path` which points to `src`. If `path` already exists, overwrite it. + +### cd(path) + +Change the current working directory. + +### pwd() + +Get the current working directory. + +#### chmod(mode, path, [callback]) + +Change permission bits on `path`. + +#### chown(uid, gid, path, [callback]) + +Change the owner of the file or directory on `path` + +#### chown_R(uid, gid, path, [callback]) + +Change the owner of the file or directory on `path` recursively. + +#### fwrite_p(path, data, [callback]) + +Write a file on `path` after create all its parent directories if necessary. diff --git a/fsautil.js b/fsautil.js new file mode 100644 index 0000000..0ca491d --- /dev/null +++ b/fsautil.js @@ -0,0 +1,136 @@ +var fs = require('fs'); +var pth = require('path'); +var async = require('async'); +var _ = require('underscore'); +var util = require('util'); + +pth.sep = pth.sep || (process.platform == 'win32' ? '\\' : '/'); + +var rm_rf = function(path, callback) { + fs.lstat(path, function(err, stat) { + if (err) { + if (err.code == 'ENOENT') { + callback(null); + } else { + callback(err); + } + } else { + if (stat.isDirectory()) { + async.series([ + function remove_entries(cb) { + fs.readdir(path, function(err, files) { + files = _.map(files, function(file) { return pth.join(path, file); }); + async.forEach(files, rm_rf, cb); + }); + }, + function remove_container(cb) { + fs.rmdir(path, cb); + }, + ], callback); + } else { + fs.unlink(path, callback); + } + } + }); +} + +var _mkdir_p = function(path_segments, callback) { + var base = ''; + async.forEachSeries(path_segments, function(segment, cb) { + base = pth.join(base, segment); + pth.exists(base, function(exists) { + if (exists) { + cb(); + } else { + fs.mkdir(base, cb); + } + }); + }, callback); +} + +var mkdir_p = function(path, callback) { + _mkdir_p(pth.normalize(path).split(pth.sep), callback); +} + +var fwrite_p = function(path, data, callback) { + var path_segments = pth.normalize(path).split(pth.sep); + _mkdir_p(path_segments.slice(0, path_segments.length - 1), function() { + fs.writeFile(path, data, callback); + }); +} + +var cp = function(src, dst, callback) { + var is = fs.createReadStream(src); + var os = fs.createWriteStream(dst); + util.pump(is, os, callback); +} + +var cp_r = function(src, dst, callback) { + var self = this; + fs.stat(src, function(err, stat) { + if (stat.isDirectory()) { + fs.mkdir(dst, function(err) { + fs.readdir(src, function(err, files) { + async.forEach(files, function(file, cb) { + self.cp_r(pth.join(src, file), pth.join(dst, file), cb); + }, callback); + }); + }); + } else { + cp(src, dst, callback); + } + }); +} + +var ln_sf = function(src, path, callback) { + pth.exists(path, function(exists) { + if (exists) { + fs.stat(path, function(err, stat) { + if (stat.isDirectory()) { + var segments = src.split(pth.sep); + filename = segments.split(pth.sep)[segments.length - 1] + fs.symlink(src, pth.join(path, filename), null, callback); + } else { + fs.unlink(path, function(err) { + fs.symlink(src, path, null, callback); + }); + } + }); + } else { + fs.symlink(src, path, null, callback); + } + }); +} + +var chown_R = function(uid, gid, path, callback) { + var self = this; + + fs.stat(path, function(err, stat) { + if (stat.isDirectory()) { + fs.readdir(path, function(err, files) { + async.forEach(files, function (filename, cb) { + self.chown_R(uid, gid, pth.join(path, filename), cb); + }, function(err) { + fs.chown(path, uid, gid, callback); + }); + }); + } else { + fs.chown(path, uid, gid, callback); + } + }); +} + +exports.rm_rf = rm_rf; +exports.mkdir_p = mkdir_p; +exports.fwrite_p = fwrite_p; +exports.cp = cp; +exports.cp_r = cp_r; +exports.ln_s = fs.symlink; +exports.ln_sf = ln_sf; +exports.cd = process.chdir; +exports.pwd = process.cwd; +exports.mv = fs.rename; +exports.rm = fs.unlink; +exports.chmod = function(mode, path, callback) { return fs.chmod(path, mode, callback); }; +exports.chown = function(uid, gid, path, callback) { return fs.chown(path, uid, gid, callback); }; +exports.chown_R = chown_R; diff --git a/package.json b/package.json new file mode 100644 index 0000000..3d00119 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "fsautil", + "version": "0.1.2", + "author": { + "name": "Yi, EungJun" + }, + "description": "Synchronous filesystem utilities", + "repository": { + "type": "git", + "url": "git://github.com/npcode/node-fsautil.git" + }, + "keywords": [ + "fs", + "fsautil", + "file", + "filesystem", + "fileutil", + "utility" + ], + "main": "./fsautil.js", + "dependencies": { + "underscore": ">=1.3.3" + }, + "devDependencies": { + "underscore": ">=1.3.3" + "mocha": ">=1.0.x" + }, + "noAnalyze": true, + "contributors": [ + { + "name": "Yi, EungJun", + "email": "semtlenori@gmail.com" + } + ], + "bundleDependencies": [], + "license": "MIT", + "engine": { + "node": ">=0.6" + }, + "scripts": { + "test": "sudo mocha -u tdd -R spec" + }, +} diff --git a/test/fsautil.test.js b/test/fsautil.test.js new file mode 100644 index 0000000..2247c7a --- /dev/null +++ b/test/fsautil.test.js @@ -0,0 +1,235 @@ +var assert = require('assert'); +var fsautil = require('../fsautil'); +var fs = require('fs'); +var path = require('path'); + +suite('fsautil.rm_rf', function() { + var cleanup = function() { + if (path.existsSync('a')) { + fs.rmdirSync('a'); + } + if (path.existsSync('a/b')) { + fs.unlinkSync('a/b'); + } + if (path.existsSync('b')) { + fs.rmdirSync('b'); + } + if (path.existsSync('c')) { + fs.unlinkSync('c'); + } + if (path.existsSync('d')) { + fs.unlinkSync('d'); + } + } + + setup(function(done) { + cleanup(); + done(); + }); + + test('Remove given directory recursively.', function(done) { + fs.mkdirSync('a'); + fs.writeFileSync('a/b', 'hello'); + fsautil.rm_rf('a', function(err) { + if (err) throw err; + assert.ok(!path.existsSync('a')); + assert.ok(!path.existsSync('a/b')); + done(); + }); + }); + + test('Remove a dead symbolic link.', function() { + fs.writeFileSync('c', 'hello'); + fs.symlinkSync('c', 'd'); + fs.unlinkSync('c'); + fsautil.rm_rf('d', function(err) { + assert.throws(function() {fs.lstatSync('d');}); + }); + }); + + teardown(function(done) { + cleanup(); + done(); + }); + +}); + +suite('fsautil.mkdir_p', function() { + setup(function(done) { + if (path.existsSync('a/b')) { + fs.rmdirSync('a/b'); + fs.rmdirSync('a'); + } + done(); + }); + + test('Create a directory and all its parent directories.', function(done) { + fsautil.mkdir_p('a/b', function(err) { + if (err) throw err; + assert.ok(path.existsSync('a')); + assert.ok(path.existsSync('a/b')); + done(); + }); + }); + + teardown(function(done) { + if (path.existsSync('a/b')) { + fs.rmdirSync('a/b'); + fs.rmdirSync('a'); + } + done(); + }); +}); + +suite('fsautil.fwrite_p', function() { + setup(function(done) { + if (path.existsSync('a/b')) { + fs.unlinkSync('a/b'); + fs.rmdirSync('a'); + } + done(); + }); + + test('Write a file after create all its parent directories.', function(done) { + fsautil.fwrite_p('a/b', 'hello', function(err) { + if (err) throw err; + assert.equal(fs.readFileSync('a/b'), 'hello'); + done(); + }); + }); + + teardown(function(done) { + if (path.existsSync('a/b')) { + fs.unlinkSync('a/b'); + fs.rmdirSync('a'); + } + done(); + }); +}); + +suite('fsautil.cp', function() { + setup(function(done) { + fsautil.rm_rf('a', function() { + fsautil.rm_rf('b', done); + }); + }); + + test('Copy a file content src to dest.', function(done) { + fs.writeFileSync('a', 'hello'); + fsautil.cp('a', 'b', function() { + assert.equal(fs.readFileSync('b'), 'hello'); + done(); + }); + }); + + teardown(function(done) { + fsautil.rm_rf('a', function() { + fsautil.rm_rf('b', done); + }); + }); +}); + +suite('fsautil.cp_r', function() { + setup(function(done) { + fsautil.rm_rf('a', function() { + fsautil.rm_rf('b', done); + }); + }); + + test('Copy src to dest recursively.', function(done) { + fs.mkdirSync('a'); + fs.mkdirSync('a/b'); + fs.mkdirSync('a/c'); + fs.writeFileSync('a/d', 'hello'); + + fsautil.cp_r('a', 'b', function() { + assert.equal(fs.readFileSync('b/d'), 'hello'); + done(); + }); + }); + + teardown(function(done) { + fsautil.rm_rf('a', function() { + fsautil.rm_rf('b', done); + }); + }); +}); + +suite('fsautil.ln_sf', function() { + setup(function(done) { + fsautil.rm_rf('a', function(err) { + fsautil.rm_rf('b', done); + }); + }); + + test('Creates a symbolic link on the given path even if a file exists on the path.', function(done) { + fs.writeFileSync('a', 'hello'); + fs.writeFileSync('b', 'bye'); + fsautil.ln_sf('a', 'b', function(err) { + if (err) throw err; + assert.ok(fs.lstatSync('b').isSymbolicLink()); + assert.equal(fs.readFileSync('b'), 'hello'); + done(); + }); + }); + + teardown(function(done) { + fsautil.rm_rf('a', function(err) { + fsautil.rm_rf('b', done); + }); + }); +}); + +suite('fsautil.cd', function() { + var origin; + + setup(function(done) { + fsautil.rm_rf('a', function(err) { + origin = process.cwd(); + done(); + }); + }); + + test('Change the current working directory.', function(done) { + fs.mkdirSync('a'); + var last_path = process.cwd(); + fsautil.cd('a'); + assert.equal(process.cwd(), path.join(last_path, 'a')); + done(); + }); + + teardown(function(done) { + process.chdir(origin); + fsautil.rm_rf('a', done); + }); +}); + +suite('fsautil.pwd', function() { + test('Get the current working directory.', function() { + assert.equal(process.cwd(), fsautil.pwd()); + }); +}); + +// this test must be ran as root. +suite('fsautil.chown_R', function() { + setup(function(done) { + fsautil.rm_rf('a', done); + }); + + test('Change the owner of given path recursively.', function(done) { + fs.mkdirSync('a'); + fs.writeFileSync('a/b', 'hello'); + fsautil.chown_R(1000, 1000, 'a', function(err) { + var stat = fs.statSync('a'); + assert.equal(fs.statSync('a').uid, 1000); + assert.equal(fs.statSync('a').gid, 1000); + assert.equal(fs.statSync('a/b').uid, 1000); + assert.equal(fs.statSync('a/b').gid, 1000); + done(); + }); + }); + + teardown(function(done) { + fsautil.rm_rf('a', done); + }); +});