pregister
Version:
register modules with glob patterns
289 lines (250 loc) • 7.19 kB
JavaScript
;
/**
* Callback as node pattern callbacks.
*
* @callback callbackPattern
*
* @returns {void}
*/
var glob = require('glob'),
path = require('path'),
mpath = require('mpath'),
async = require('async'),
debug = require('debug')('pregister');
var Pregister = (function () {
var bucket = {};
/**
*
* @param {String} file
* @param {Object} [options]
* @param {String} [options.cwd]
* @private
* @access private
* @returns {*}
*/
function cleanFile(file, options) {
options = options || {};
var cwd = options.cwd || process.cwd();
return path.join(cwd, path.normalize(file).replace(cwd, ''));
}
/**
*
* @param {String} namespace
* @param {String} file
* @param {Object} [options]
* @private
* @access private
*/
function registerFile(namespace, file, options) {
var cleanedFile = cleanFile(file, options),
moduleNamespace;
moduleNamespace = Pregister.file2namespace(file, namespace); // cut sufix
// load module
try {
register(moduleNamespace, require(cleanedFile), options);
} catch (err) {
console.error('PREGISTER Error on require: \n', cleanedFile, '\n\n', err.stack || err);
}
}
/**
*
* @param {String} file
* @param {Object} options
* @param {callbackPattern} done
* @private
* @access private
* @returns {void}
*/
function callFile(file, options, done) {
var moduleRequire;
debug('CALL - %s', file);
// load module
try {
moduleRequire = require(cleanFile(file, options));
moduleRequire = moduleRequire.default || moduleRequire;
} catch (err) {
console.error('PREGISTER Error on require: \n', cleanFile(file, options), '\n\n', err.stack || err);
}
// with wrapper
if (moduleRequire && typeof options.invoke === 'function') {
try {
options.invoke.call(undefined, moduleRequire);
} catch (err) {
console.error('PREGISTER Error on wrapper: \n', cleanFile(file, options), '\n\n', err.stack || err);
}
} else if(moduleRequire) {
try {
var args = (Array.isArray(options.args) && options.args.concat()) || [];
moduleRequire.apply && moduleRequire.apply(undefined, args);
} catch (err) {
console.error('PREGISTER ERROR apply: \n', cleanFile(file, options), '\n\n', err.stack || err);
}
}
moduleRequire = null;
// all is okay
done && done();
}
/**
*
* @param {String} namespace - if not set, then use root node
* @param {*} part - register module in bucket scope
* @param {Object} [options] - Call Module as Singleton
* @param {Function} [options.singleton] - Call Module as Singleton
*
* @private
* @access private
*
* @throws Error
*
* @returns {void}
*/
function register(namespace, part, options) {
var scope = bucket, moduleName, moduleNamespace;
if(!namespace) {
throw new Error('Namespace can not be empty');
}
moduleNamespace = namespace.split('.');
// get last node and
// transform my-function to myFunction
moduleName = moduleNamespace.pop().replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
// create object path
moduleNamespace.forEach(function (node) {
scope = scope[node] = scope[node] || {};
});
// prevent duplicate register module
if (scope[moduleName]) {
throw new Error('Module "' + moduleName + '" in Namespace "' + namespace + '" exist');
}
debug({
namespace: namespace,
moduleNamespace: moduleNamespace,
moduleKey: moduleName
});
if(options && options.singleton) {
scope[moduleName] = options.singleton(part.default || part);
} else {
// load module
scope[moduleName] = part.default || part;
}
}
return {
/**
* @method
* @public
* @access public
*
* @param {String} namespace
* @param {String} pattern
* @param {Object|Function} [options] - glob options or intern options
* @param {Function} [done]
*/
require: function (namespace, pattern, options, done) {
var resolvable;
options = options || {};
if (typeof options === 'function') {
done = options;
options = {};
}
if(typeof pattern === 'object') {
return register(namespace, pattern, options);
}
try{
require.resolve(pattern);
resolvable = true;
} catch(error) {
resolvable = false;
}
if(resolvable) {
return register(namespace, require(pattern), options);
}
async.each(glob.sync(pattern, options) || [], function (file, done) {
registerFile(namespace, file, options);
done();
}, done);
},
/**
* @method
* @public
* @access public
*
* @param {String} namespace
* @param {*} part
* @param {Object} [options]
* @param {Function} [options.singleton]
*/
register: function(namespace, part, options) {
return register(namespace, part, options);
},
/**
* @method
* @public
* @access public
*
* @param {String|undefined} namespace
* @param {*|undefined} [def] - dafault
*/
resolve: function (namespace, def) {
var res = (!namespace) ? bucket : mpath.get(namespace, bucket);
if (!res && !def) {
throw new Error('PregisterResolveError: ' + namespace);
}
return res || def;
},
/**
*
* @param {String} namespace
*
* @returns {Pregister}
*/
remove: function(namespace) {
var path = namespace.split('.');
var last = path.pop();
var data = bucket;
path.forEach(function(path) {
data = data[path];
});
delete data[last];
return this;
},
/**
* @returns {Pregister}
*/
reset: function() {
bucket = {};
return this;
},
/**
* @method
*
* @public
* @access private
*
* @param {String} pattern - glob pattern
* @param {Object} options - glob options
* @param {String} [options.cwd] - cwd path
* @param {Function} [done] - callback
*/
call: function (pattern, options, done) {
async.each(
glob.sync(pattern, options) || [], function (file, done) {
callFile(file, options, done);
}, done || function () {}
);
},
/**
*
* @param {String} file
* @param {String} namespace
*/
file2namespace: function(file, namespace) {
var path = file
.replace(/(\/)/g, '.') // replace / => .
.replace(/\.\w+$/, '') // cut extension .js
.replace(/\.index$/, '') // remove default index module name
.replace(/^\.|\.$/, ''); // trim .
return (namespace + '.' + path.replace(new RegExp('(.*)\\.+' + namespace, 'i'), namespace))
.replace(new RegExp('\\.' + namespace), '').replace(/\.{2,}/, '.');
}
};
})();
module.exports = Pregister;