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
335 lines
8.0 KiB
5 months ago
|
/**
|
||
|
* @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);
|
||
|
});
|
||
|
};
|