追記 2015-05-03
delといふライブラリを使ふと便利だ。glob指定ができる。使ふべきだ。
cf. del https://www.npmjs.com/package/del
node.jsのFile System moduleには、directoryを再帰的に扱うAPIが無いから、自分で再帰を書く。然も遅延させて再帰するから結構アレ。
cf. node.jsで非同期にdirectory中の全てのfileを読み込む http://c4se.hatenablog.com/entry/2013/10/18/124837
rm -rf .
に当たる処理を書いた。結構アレ。
/** * Extends standard FileSystem module. * * <p><a href="http://c4se.hatenablog.com/entry/2013/10/18/124837"> * node.jsで非同期にdirectory中の全てのfileを読み込む - c4se記:さっちゃんですよ☆</a></p> * <p><a href="http://c4se.hatenablog.com/entry/2013/10/25/222643"> * node.jsでdirectoryを再帰的に強制削除 (rm -rf) - c4se記:さっちゃんですよ☆</a></p> * * @author ne_Sachirou <utakata.c4se@gmail.com> * @license Public Domain * @module fs */ 'use strict'; var fs = require('fs'), path = require('path'), Q = require('q'); /** * rmdir -rf (recursive, force) async. * * @static * @param {string} dirpath * @param {function(?Error)} callback */ function rmdirRF(dirpath, callback) { rmdirRFRecur(dirpath). then(function() { callback(null); }). fail(function(error) { callback(error); }). done(); } /** * Remove a directory that is not empty in NodeJS ≪ geedew * http://www.geedew.com/2012/10/24/remove-a-directory-that-is-not-empty-in-nodejs/ * * @static * @param {string} dirpath */ function rmdirRFSync(dirpath) { if (fs.existsSync(dirpath)) { fs.readdirSync(dirpath).forEach(function(filepath) { filepath = path.join(dirpath, filepath); if (fs.lstatSync(filepath).isDirectory()) { rmdirRFSync(filepath); } else { fs.unlinkSync(filepath); } }); fs.rmdirSync(dirpath); } } /* * @param {string} dirpath * @return {Q.defer.promise} */ function rmdirRFRecur(dirpath) { var deferred = Q.defer(); fs.exists(dirpath, function(isExists) { if (! isExists) { deferred.resolve(); return; } fs.lstat(dirpath, function(error, stats) { var promise; if (error) { deferred.reject(error); return; } if (stats.isDirectory()) { promise = removeDirectory(dirpath); } else { promise = removeFile(dirpath); } promise. then(deferred.resolve.bind(deferred)). fail(deferred.reject.bind(deferred)). done(); }); }); return deferred.promise; } function removeDirectory(dirpath) { var deferred = Q.defer(); fs.readdir(dirpath, function(error, files) { var promises = [ ]; if (error) { deferred.reject(error); return; } files.forEach(function(filepath) { filepath = path.join(dirpath, filepath); promises.push(rmdirRFRecur(filepath)); }); Q.all(promises).then(function() { fs.rmdir(dirpath, function(error) { if (error) { deferred.reject(error); return; } deferred.resolve(); }); }).fail(deferred.reject.bind(deferred)).done(); }); return deferred.promise; } function removeFile(filepath) { var deferred = Q.defer(); fs.unlink(filepath, function(error) { if (error) { deferred.reject(error); return; } deferred.resolve(); }); return deferred.promise; } fs.rmdirRF = rmdirRF; fs.rmdirRFSync = rmdirRFSync; module.exports = fs;
因みにChild Processでrm -rf
を走らせる案は、Windowsで動かないので死にました。
Test
裏ではtest書いてるよ。でもfactoryが実際にdirectoryやfileを作ったり消したりしてて、クッソきちゃないのでアレ。
見ないで下さい。
factory.js
'use strict'; var fs = require('fs'), path = require('path'), Q = require('q'), lib = require('..'); /** * @constructor * @prop {Array.<function()>} destructors */ function factory() { if (! (this instanceof factory)) { return new factory(); } if (factory.instance) { return factory.instance; } factory.instance = this; this.destructors = [ ]; } /** @type {factory} */ factory.instance = null; /** * Create a product. * * @param {string} name * @param {...Object} * @return {Object} */ factory.prototype.create = function(name) { return factory[name].apply(this, Array.prototype.slice.call(arguments, 1)); }; /** * Destruct all products. * * @return {factory} */ factory.prototype.destroy = function() { this.destructors. forEach(function(destructor) { destructor.call(this); }.bind(this)); this.destructors = [ ]; return this; }; factory.files = function(dirpath) { var deferred = Q.defer(), promises = [ ]; dirpath = dirpath || path.join(__dirname, 'tmp'); ['.', 'sub', 'sub/sub'].forEach(function(subDirpath) { subDirpath = path.join(dirpath, subDirpath); fs.mkdirSync(subDirpath); promises.push(factory.files._createTmpFiles(subDirpath, factory.files._DATAS)); }); Q.all(promises). then(function() { deferred.resolve(factory.files._DATAS); }). fail(deferred.reject.bind(deferred)); this.destructors.push(function() { lib.fs.rmdirRFSync(dirpath); }); return deferred.promise; }; factory.files._DATAS = [ { name: 'index.html', data: '<!DOCTYPE html>\n' + '<html>\n'+ ' <head lang="en">\n'+ ' <meta charset="utf-8/">\n' + ' <title>Index</title>\n' + ' <link rel="stylesheet" href="index.css"/>\n' + ' </head>\n' + ' <body>\n' + ' <p>Body</p>\n' + ' <script src="index.js"></script>\n' + ' </body>\n' + '</html>\n' }, { name: 'index.css', data: '@charset "utf-8";\n\n' + 'p {\n' + ' margin: 1em;\n' + ' padding: 1em;\n' + '}\n' }, { name: 'index.js', data: 'console.log("loaded");' } ]; factory.files._createTmpFiles = function(dirpath, datas) { var deferred = Q.defer(), promises = [ ]; datas.forEach(function(data) { var deferred = Q.defer(), filepath = path.join(dirpath, data.name); fs.writeFile(filepath, data.data, function(error) { if (error) { deferred.reject(error); return; } deferred.resolve(); }); promises.push(deferred.promise); }); Q.all(promises). then(deferred.resolve.bind(deferred)). fail(deferred.reject.bind(deferred)); return deferred.promise; }; module.exports = factory;
fs_test.js
'use strict'; var fs = require('../..').fs, factory = require('../factory.js')(); exports.testFsReadAllFilesWorks = function(test) { test.expect(4); factory.create('files').then(function(datas) { fs.readAllFiles(__dirname + '/../' + 'tmp', function(error, files) { if (error) { test.ifError(error); factory.destroy(); test.done(); return; } test.equal(files.length, 9); files = files.map(function(file) { return file.toString(); }); test.equal( files.filter(function(file) { return file === datas[0].data; }).length, 3); test.equal( files.filter(function(file) { return file === datas[1].data; }).length, 3); test.equal( files.filter(function(file) { return file === datas[2].data; }).length, 3); factory.destroy(); test.done(); }); }).fail(function(error) { test.ifError(error); factory.destroy(); test.done(); }); }; exports.testFsRmdirRfWorks = function(test) { test.expect(1); factory.create('files').then(function() { fs.rmdirRF(__dirname + '/../' + 'tmp', function(error) { if (error) { test.ifError(error); factory.destroy(); test.done(); return; } test.ok(! fs.existsSync(__dirname + '/../' + 'tmp')); factory.destroy(); test.done(); }); }).fail(function(error) { test.ifError(error); factory.destroy(); test.done(); }); };