work.suroh.tk/node_modules/find/index.js

426 lines
9.5 KiB
JavaScript
Raw Normal View History

2019-12-02 12:22:45 +00:00
var fs = require('fs');
var path = require('path');
var Chain = require('traverse-chain');
/**
* Outline the APIs.
*/
var find = module.exports = {
// file: function([pat,] root, callback) {}
// dir: function([pat,] root, callback) {}
// eachfile: function([pat,] root, action) {}
// eachdir: function([pat,] root, action) {}
// fileSync: function([pat,] root) {}
// dirSync: function([pat,] root) {}
// use:: function(options) {}
};
var fss = {};
/**
* Error handler wrapper.
*/
fss.errorHandler = function(err) {
if (err) {
if (find.__errorHandler) {
find.__errorHandler(err);
} else {
throw err;
}
}
};
var error = {
notExist: function(name) {
return new Error(name + ' does not exist.');
}
};
var is = (function() {
function existed(name) {
return fs.existsSync(name);
}
function fsType(type) {
return function(name) {
try {
return fs.lstatSync(name)['is' + type]();
} catch(err) {
if (!/^(EPERM|EACCES)$/.test(err.code)) {
fss.errorHandler(err);
}
else {
console.warn('Warning: Cannot access %s', name);
}
}
}
}
function objType(type) {
return function(input) {
if (type === 'Function') {
return typeof input === 'function';
}
return ({}).toString.call(input) === '[object ' + type + ']';
}
}
return {
existed: existed,
file: fsType('File'),
directory: fsType('Directory'),
symbolicLink: fsType('SymbolicLink'),
string: objType('String'),
regexp: objType('RegExp'),
func: objType('Function')
};
}());
/**
* Method injection for handling errors.
*/
['readdir', 'lstat'].forEach(function(method) {
fss[method] = function(path, callback) {
var origin = fs[method];
return origin.apply(fs, [path, function(err) {
fss.errorHandler(err);
return callback.apply(null, arguments);
}]);
}
});
/**
* Enhancement for fs.readlink && fs.readlinkSync.
*/
fss.readlink = function(name, fn, depth) {
if (depth == undefined) depth = 5;
if (!is.existed(name) && (depth < 5)) {
return fn(path.resolve(name));
}
var isSymbolicLink = is.symbolicLink(name);
if (!isSymbolicLink) {
fn(path.resolve(name));
} else if (depth) {
fs.realpath(name, function(err, origin) {
if (err && /^(ENOENT|ELOOP|EPERM|EACCES)$/.test(err.code)) {
fn(name);
} else {
if (err) {
fss.errorHandler(err);
} else {
fss.readlink(origin, fn, --depth);
}
}
});
} else {
fn(isSymbolicLink ? '' : path.resolve(name));
}
}
fss.readlinkSync = function(name, depth) {
if (depth == undefined) depth = 5;
if (!is.existed(name) && depth < 5) {
return path.resolve(name);
}
var isSymbolicLink = is.symbolicLink(name);
if (!isSymbolicLink) {
return path.resolve(name);
} else if (depth) {
var origin;
try {
origin = fs.realpathSync(name);
} catch (err) {
if (/^(ENOENT|ELOOP|EPERM|EACCES)$/.test(err.code)) {
return name;
} else {
fss.errorHandler(err);
}
}
return fss.readlinkSync(origin, --depth);
} else {
return isSymbolicLink ? '' : path.resolve(name);
}
}
/**
* Check pattern against the path
*/
var compare = function(pat, name) {
var str = path.basename(name);
return (
is.regexp(pat) && pat.test(name)
|| is.string(pat) && pat === str
);
};
/**
* Traverse a directory recursively and asynchronously.
*
* @param {String} root
* @param {String} type
* @param {Function} action
* @param {Function} callback
* @param {Chain} c
* @api private
*/
var traverseAsync = function(root, type, action, callback, c) {
if (!is.existed(root)) {
fss.errorHandler(error.notExist(root))
}
var originRoot = root;
if (is.symbolicLink(root)) {
root = fss.readlinkSync(root);
}
if (is.directory(root)) {
fss.readdir(root, function(err, all) {
var chain = Chain();
all && all.forEach(function(dir) {
dir = path.join(originRoot, dir);
chain.add(function() {
var handleFile = function() {
if (type == 'file') action(dir);
process.nextTick(function() { chain.next() });
}
var handleDir = function(skip) {
if (type == 'dir') action(dir);
if (skip) chain.next();
else process.nextTick(function() { traverseAsync(dir, type, action, callback, chain)});
}
var isSymbolicLink = is.symbolicLink(dir);
if (is.directory(dir)) {
handleDir();
} else if (isSymbolicLink) {
fss.readlink(dir, function(origin) {
if (origin) {
if (is.existed(origin) && is.directory(origin)) {
handleDir(isSymbolicLink)
} else {
handleFile()
}
} else {
chain.next();
}
});
} else {
handleFile();
}
})
});
chain.traverse(function() {
c ? c.next() : callback();
});
});
}
}
/**
* Traverse a directory recursively.
*
* @param {String} root
* @param {String} type
* @param {Function} action
* @return {Array} the result
* @api private
*/
var traverseSync = function(root, type, action) {
if (!is.existed(root)) throw error.notExist(root);
var originRoot = root;
if (is.symbolicLink(root)) {
root = fss.readlinkSync(root);
}
if (is.directory(root)) {
fs.readdirSync(root).forEach(function(dir) {
dir = path.join(originRoot, dir);
var handleDir = function(skip) {
if (type == 'dir') action(dir);
if (skip) return;
traverseSync(dir, type, action);
}
var handleFile = function() {
if (type == 'file') action(dir);
}
var isSymbolicLink = is.symbolicLink(dir);
if (is.directory(dir)) {
handleDir();
} else if (isSymbolicLink) {
var origin = fss.readlinkSync(dir);
if (origin) {
if (is.existed(origin) && is.directory(origin)) {
handleDir(isSymbolicLink);
} else {
handleFile();
}
}
} else {
handleFile();
}
});
}
};
['file', 'dir'].forEach(function(type) {
/**
* `find.file` and `find.dir`
*
* Find files or sub-directories in a given directory and
* passes the result in an array as a whole. This follows
* the default callback style of nodejs, think about `fs.readdir`,
*
* @param {RegExp|String} pat
* @param {String} root
* @param {Function} fn
* @api public
*/
find[type] = function(pat, root, fn) {
var buffer = [];
if (arguments.length == 2) {
fn = root;
root = pat;
pat = '';
}
process.nextTick(function() {
traverseAsync(
root
, type
, function(n) { buffer.push(n);}
, function() {
if (is.func(fn) && pat) {
fn(buffer.filter(function(n) {
return compare(pat, n);
}));
} else {
fn(buffer);
}
}
);
});
return {
error: function(handler) {
if (is.func(handler)) {
find.__errorHandler = handler;
}
}
}
}
/**
* `find.eachfile` and `find.eachdir`
*
* Find files or sub-directories in a given directory and
* apply with a given action to each result immediately
* rather than pass them back as an array.
*
* @param {RegExp|String} pat
* @param {String} root
* @param {Function} action
* @return {Object} for chain methods
* @api public
*
*/
find['each' + type] = function(pat, root, action) {
var callback = function() {}
if (arguments.length == 2) {
action = root;
root = pat;
pat = '';
}
process.nextTick(function() {
traverseAsync(
root
, type
, function(n) {
if (!is.func(action)) return;
if (!pat || compare(pat, n)) {
action(n);
}
}
, callback
);
});
return {
end: function(fn) {
if (is.func(fn)) {
callback = fn;
}
return this;
},
error: function(handler) {
if (is.func(handler)) {
find.__errorHandler = handler;
}
return this;
}
};
}
/**
* `find.fileSync` and `find.dirSync`
*
* Find files or sub-directories in a given directory synchronously
* and returns the result as an array. This follows the default 'Sync'
* methods of nodejs, think about `fs.readdirSync`,
*
* @param {RegExp|String} pat
* @param {String} root
* @return {Array} the result
* @api public
*/
find[type + 'Sync'] = function(pat, root) {
var buffer = [];
if (arguments.length == 1) {
root = pat;
pat = '';
}
traverseSync(root, type, function(n) {
buffer.push(n);
});
return pat && buffer.filter(function(n) {
return compare(pat, n);
}) || buffer;
}
});
var fsMethods = [
'existsSync',
'lstatSync',
'realpath',
'realpathSync',
'readdir',
'readdirSync'
];
/**
* Configuations for internal usage
*
* @param {Object} options
* @api public
*/
find.use = function(options) {
if (options && options.fs) {
if (fsMethods.every(n => !!options.fs[n])) {
fs = options.fs;
} else {
throw new Error('The provided fs object is not compatiable with native fs.');
}
}
return find;
}