/** * @fileoverview Strengthen the ability of file system * @author wliao */ var fs = require('fs'); var util = require('utils-extend'); var path = require('path'); var fileMatch = require('file-match'); function checkCbAndOpts(options, callback) { if (util.isFunction(options)) { return { options: null, callback: options }; } else if (util.isObject(options)) { return { options: options, callback: callback }; } else { return { options: null, callback: util.noop }; } } function getExists(filepath) { var exists = fs.existsSync(filepath); if (exists) { return filepath; } else { return getExists(path.dirname(filepath)); } } util.extend(exports, fs); /** * @description * Assign node origin methods to fs */ exports.fs = fs; exports.fileMatch = fileMatch; /** * @description * Create dir, if dir exist, it will only invoke callback. * * @example * ```js * fs.mkdir('1/2/3/4/5', 511); * fs.mkdir('path/2/3', function() {}); * ``` */ exports.mkdir = function(filepath, mode, callback) { var root = getExists(filepath); var children = path.relative(root, filepath); if (util.isFunction(mode)) { callback = mode; mode = null; } if (!util.isFunction(callback)) { callback = util.noop; } mode = mode || 511; if (!children) return callback(); children = children.split(path.sep); function create(filepath) { if (create.count === children.length) { return callback(); } filepath = path.join(filepath, children[create.count]); fs.mkdir(filepath, mode, function(err) { create.count++; create(filepath); }); } create.count = 0; create(root); }; /** * @description * Same as mkdir, but it is synchronous */ exports.mkdirSync = function(filepath, mode) { var root = getExists(filepath); var children = path.relative(root, filepath); if (!children) return; children = children.split(path.sep); children.forEach(function(item) { root = path.join(root, item); fs.mkdirSync(root, mode); }); }; /** * @description * Create file, if path don't exists, it will not throw error. * And will mkdir for path, it is asynchronous * * @example * ```js * fs.writeFile('path/filename.txt', 'something') * fs.writeFile('path/filename.txt', 'something', {}) * ``` */ exports.writeFile = function(filename, data, options, callback) { var result = checkCbAndOpts(options, callback); var dirname = path.dirname(filename); options = result.options; callback = result.callback; // Create dir first exports.mkdir(dirname, function() { fs.writeFile(filename, data, options, callback); }); }; /** * @description * Same as writeFile, but it is synchronous */ exports.writeFileSync = function(filename, data, options) { var dirname = path.dirname(filename); exports.mkdirSync(dirname); fs.writeFileSync(filename, data, options); }; /** * @description * Asynchronously copy a file * @example * file.copyFile('demo.txt', 'demo.dest.txt', { done: function(err) { }}) */ exports.copyFile = function(srcpath, destpath, options) { options = util.extend({ encoding: 'utf8', done: util.noop }, options || {}); if (!options.process) { options.encoding = null; } fs.readFile(srcpath, { encoding: options.encoding }, function(err, contents) { if (err) return options.done(err); if (options.process) { contents = options.process(contents); } exports.writeFile(destpath, contents, options, options.done); }); }; /** * @description * Copy file to dest, if no process options, it will only copy file to dest * @example * file.copyFileSync('demo.txt', 'demo.dest.txt' { process: function(contents) { }}); * file.copyFileSync('demo.png', 'dest.png'); */ exports.copyFileSync = function(srcpath, destpath, options) { options = util.extend({ encoding: 'utf8' }, options || {}); var contents; if (options.process) { contents = fs.readFileSync(srcpath, options); contents = options.process(contents, srcpath, options.relative); if (util.isObject(contents) && contents.filepath) { destpath = contents.filepath; contents = contents.contents; } exports.writeFileSync(destpath, contents, options); } else { contents = fs.readFileSync(srcpath); exports.writeFileSync(destpath, contents); } }; /** * @description * Recurse into a directory, executing callback for each file and folder * if the filename is undefiend, the callback is for folder, otherwise for file. * and it is asynchronous * @example * file.recurse('path', function(filepath, filename) { }); * file.recurse('path', ['*.js', 'path/**\/*.html'], function(filepath, relative, filename) { }); */ exports.recurse = function(dirpath, filter, callback) { if (util.isFunction(filter)) { callback = filter; filter = null; } var filterCb = fileMatch(filter); var rootpath = dirpath; function recurse(dirpath) { fs.readdir(dirpath, function(err, files) { if (err) return callback(err); files.forEach(function(filename) { var filepath = path.join(dirpath, filename); fs.stat(filepath, function(err, stats) { var relative = path.relative(rootpath, filepath); var flag = filterCb(relative); if (stats.isDirectory()) { recurse(filepath); if (flag) callback(filepath, relative); } else { if (flag) callback(filepath, relative, filename); } }); }); }); } recurse(dirpath); }; /** * @description * Same as recurse, but it is synchronous * @example * file.recurseSync('path', function(filepath, filename) {}); * file.recurseSync('path', ['*.js', 'path/**\/*.html'], function(filepath, relative, filename) {}); */ exports.recurseSync = function(dirpath, filter, callback) { if (util.isFunction(filter)) { callback = filter; filter = null; } var filterCb = fileMatch(filter); var rootpath = dirpath; function recurse(dirpath) { fs.readdirSync(dirpath).forEach(function(filename) { var filepath = path.join(dirpath, filename); var stats = fs.statSync(filepath); var relative = path.relative(rootpath, filepath); var flag = filterCb(relative); if (stats.isDirectory()) { recurse(filepath); if (flag) callback(filepath, relative); } else { if (flag) callback(filepath, relative, filename); } }); } recurse(dirpath); }; /** * @description * Remove folder and files in folder, but it's synchronous * @example * file.rmdirSync('path'); */ exports.rmdirSync = function(dirpath) { exports.recurseSync(dirpath, function(filepath, relative, filename) { // it is file, otherwise it's folder if (filename) { fs.unlinkSync(filepath); } else { fs.rmdirSync(filepath); } }); fs.rmdirSync(dirpath); }; /** * @description * Copy dirpath to destpath, pass process callback for each file hanlder * if you want to change the dest filepath, process callback return { contents: '', filepath: ''} * otherwise only change contents * @example * file.copySync('path', 'dest'); * file.copySync('src', 'dest/src'); * file.copySync('path', 'dest', { process: function(contents, filepath) {} }); * file.copySync('path', 'dest', { process: function(contents, filepath) {} }, noProcess: ['']); */ exports.copySync = function(dirpath, destpath, options) { options = util.extend({ encoding: 'utf8', filter: null, noProcess: '' }, options || {}); var noProcessCb = fileMatch(options.noProcess); // Make sure dest root exports.mkdirSync(destpath); exports.recurseSync(dirpath, options.filter, function(filepath, relative, filename) { if (!filename) return; var newpath = path.join(destpath, relative); var opts = { relative: relative }; if (options.process && !noProcessCb(relative)) { opts.encoding = options.encoding; opts.process = options.process; } exports.copyFileSync(filepath, newpath, opts); }); };