UNPKG

majic

Version:

A micro javascript/node.js ioc dependency injection container

245 lines (207 loc) 7.83 kB
var _ = require('lodash'), q = require('bluebird'), fs = require('fs'), glob = require('glob'), introspect = require('introspect'), appRootPath = require('app-root-path'), log = require('./log.js'), TimeoutError = q.TimeoutError; function defer() { var deferred = { }; deferred.promise = new q(function() { deferred.resolve = arguments[0]; deferred.reject = arguments[1]; }); return deferred; } function isClass(v) { return typeof v === 'function' && /^\s*class\s+/.test(v.toString()); } var Majic = function options(opts, defs) { if (!_.isObject(opts)) opts = { root: opts }; var root = _.defaults({}, opts, defs, { root: appRootPath }); var fileopts = {} try { fileopts = require(root.root + '/majic.json'); } catch (error) { if (error.message.indexOf("Cannot find") < 0) throw error; } _.defaults(this, opts, defs, fileopts, { PREFIX: "majic:", ready: defer(), container: {}, root: root.root, pkgroot: appRootPath, require: [], verbose: true, timeout: 4000, scan: [ "config/**", "src/config/**", "src/lib/**", "src/main/**" ] }); } Majic.prototype.declare = function(name, timeout, override) { var v = override ? undefined : this.container[name]; if (!v) { this.container[name] = v = defer(); if (timeout) { v.promise.timeout(timeout).catch(TimeoutError, function() { if (this.verbose) { var found = v.found ? '' : "(not found)"; log.warn(this.PREFIX, "timeout resolving", name, found); } }.bind(this)); } } return v; } Majic.prototype.declared = function(name) { return this.container[name]; } Majic.prototype.define = function(name, object) { this.declare(name).found = true; this.declare(name).resolve(object); if (this.verbose) log.info(this.PREFIX, "defined module", name, "from global") } Majic.prototype.resolve = function(name, timeout) { return this.declare(name, timeout).promise; } Majic.prototype.alias = function(src, dst) { this.resolve(src).then(function(v) { if (this.verbose) log.info(this.PREFIX, "aliased module", dst, "from", src); this.declare(dst).found = true; this.declare(dst).resolve(v); }.bind(this)); } Majic.prototype.inject = function(fn, name) { var params = introspect(fn); return this.ready.promise.then(function() { return q.all(_.map(params, function(p) { return this.resolve(p, this.timeout) }.bind(this))).then(function(args) { if (this.verbose && name) log.info(this.PREFIX, "resolving module", name, "with args", params); return fn.apply(null, args); }.bind(this)); }.bind(this)); } Majic.prototype.crequire = function(name, path, module, override) { if (_.indexOf(this.exclude, name) >= 0) return; name = name.replace(/\-/gi, '_'); var from = module ? "from npm" : "from" if (this.container[name] && !override) { return this.container[name].promise; } var deferred = this.declare(name, module ? undefined : this.timeout, false); var req = require(path); deferred.found = true; if (_.isFunction(req) && !module && !isClass(req)) { if (this.verbose) log.info(this.PREFIX, "loading module", name, from, path); this.inject(req, name).then(deferred.resolve); } else { if (this.verbose) log.info(this.PREFIX, "defined module", name, from, path); deferred.resolve(req); } return deferred.promise; } Majic.prototype.nrequire = function(name) { this.declare(name).resolve(require(name)); if (this.verbose) log.info(this.PREFIX, "defined module", name, "from node require"); } Majic.prototype.load_module = function(file, override) { return new q(function(resolve, reject) { fs.stat(file, function(err, stats) { if (err) { return reject(err); } var path = file.split("/"); var name = path[path.length-1].split('.')[0] if (stats.isFile()) { this.crequire(name, file, false, override); } resolve(true); }.bind(this)); }.bind(this)); } Majic.prototype.scan_paths = function(globs, override) { return q.all(_.map(globs, function(pattern) { return new q(function(resolve, reject) { var path = this.root+"/"+pattern if (this.verbose) log.info(this.PREFIX, "scanning path", path, "for modules") glob(path, function(err, files) { if (err) { return reject(err); } q.all(_.map(files, function(f) { // hack for startup specifiers if (this.filter && pattern == 'src/main/**') { if (f.indexOf(this.filter) < 0) { return; } } return this.load_module(f, override); }.bind(this))).then(resolve, reject); }.bind(this)); }.bind(this)); }.bind(this))); } Majic.prototype.dependencies = function(dependencies) { return q.all(_.map(dependencies, function (version, name) { if (name == 'Majic') { return; } var resolved = this.crequire(name, name, true, true); var done = [ resolved ]; if (name == "coffee-script") { require('coffee-script/register'); } if (name == "chai") { done.push(resolved.then(function (chai) { this.declare('expect').resolve(chai.expect); }.bind(this))); } if (name == "chai-as-promised") { done.push(q.all([this.resolve("chai"), resolved]).spread(function (chai, p) { chai.use(p); })); } return q.all(done); }.bind(this))); } Majic.prototype.init = function() { this.package = require(this.pkgroot + '/package.json'); this.alias('bluebird', 'q'); this.alias('lodash', '_'); this.alias('underscore', '_'); this.define('process', process) this.define('argv', process.argv); this.define('package', this.package); _.each(this.mocks, function(v, k) { this.define(k, v); }.bind(this)); return new q(function (resolve) { if (this.package.dependencies) { resolve(this.dependencies(this.package.dependencies)); } else { resolve(undefined); } }.bind(this)).then(function () { if (!this.package.cio) { this.package.cio = {} }; return this.scan_paths(this.package.cio.scan || this.scan).then(function (result) { // node dependencies _.each(this.require.concat(this.package.declare || []), function (dependency) { this.nrequire(dependency); }.bind(this)); return result; }.bind(this)); }.bind(this)); } Majic.prototype.start = function() { this.ready.resolve(this); return this; } module.exports = { start: function (opts) { if (opts === null || opts === void(0)) { opts = {}; } var majic = new Majic(opts); return majic.init().then(majic.start.bind(majic)); }, test: function test(opts) { var majic = new Majic(opts, { scan: [ "test/mock/**", "config/**", "src/config/**", "src/lib/**" ], verbose: false }); majic.init().then(function() { if (majic.package.devDependencies) { return majic.dependencies(majic.package.devDependencies); } else { return undefined; } }).then(majic.start.bind(majic)); return function(fn) { return function() { return majic.inject(fn, "injected test"); } } } }