You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

335 lines
8.0 KiB

/**
* @fileoverview Strengthen the ability of file system
* @author wliao <wliao@Ctrip.com>
*/
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);
});
};