'use strict';
var flatten = require('arr-flatten');
var args = require('fn-args');
var methods = require('methods');
var asyncLib = require('async');
var Dag = require('dag');
var needInject = require('./utils.js').needInject;
module.exports = function (app) {
var dag = new Dag();
var inject = function (dependency, fn) {
if (typeof fn !== 'function') {
throw new Error('inject() requires a function, but got a ' + typeof fn);
}
args(fn).forEach(function (param) {
dag.addEdge(dependency, param);
});
inject.declare(dependency, fn);
};
inject.dependencies = {};
inject.declare = function declare(name, fn) {
this.dependencies[name] = fn;
};
inject.resolve = function resolve(name, cb) {
var resolved = this.dependencies[name];
if (!resolved) {
return cb(new Error('Unknown dependency: ' + name));
}
return cb(null, resolved);
};
function resolveInjections(params, req, res, next, done) {
asyncLib.map(params, function (dependency, callback) {
if (dependency === 'req') {
return callback(null, req);
}
if (dependency === 'res') {
return callback(null, res);
}
if (dependency === 'next') {
return callback(null, next);
}
inject.resolve(dependency, function (err, constructor) {
if (err) {
throw err;
}
resolveInjections(
args(constructor),
req,
res,
callback,
function (err, results) {
if (err) {
return done(err);
}
constructor.apply(null, results);
}
);
});
}, done);
}
app.lazyrouter();
var _route = app._router.route.bind(app._router);
app._router.route = function (path) {
var route = _route(path);
methods.forEach(function (method) {
route[method] = wrap(route[method]);
});
return route;
};
function wrap(origin) {
return function () {
var callbacks = flatten([].slice.call(arguments));
callbacks = callbacks.map(function (fn) {
if (typeof fn !== 'function') {
return fn;
}
var params = args(fn);
if (!needInject(params)) {
return fn;
}
return function (req, res, next) {
resolveInjections(params, req, res, next, function (err, results) {
if (err) {
return next(err);
}
fn.apply(null, results);
});
};
});
origin.call(this, callbacks);
};
}
return inject;
};
/*
* This file is part of the Medic-Injector library.
*
* (c) Olivier Philippon <https://github.com/DrBenton>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Strongly inspired by the SwiftSuspenders (https://github.com/tschneidereit/SwiftSuspenders)
* ActionScript 3 library.
*/
(function(context) {
var myDebug = false;
/**
* @class InjectionMapping
*/
var FORBIDDEN_INJECTIONS_NAMES = [
'injectionValue'
];
/**
* You cant' use this constructor directly. Use Injector.addMapping to create a new "synchronous operations only"
* Injection Mapping.
*
* @class sync.InjectionMapping
* @constructor
* @param {sync.Injector} injectorInstance
* @param {String} injectionName
* @return {sync.InjectionMapping}
*/
var InjectionMapping = function (injectorInstance, injectionName)
{
if (! (injectorInstance instanceof Injector)) {
throw new Error('Don\'t instantiate InjectionMapping directly ; use Injector#addMapping to create InjectionMappings!');
}
if (-1 < FORBIDDEN_INJECTIONS_NAMES.indexOf(injectionName)) {
throw new Error('Injection name "'+injectionName+'" is forbidden');
}
this._injector = injectorInstance;
/**
*
* @type {String}
*/
this.injectionName = injectionName;
};
/**
* The simplest Injection Mapping type : each time a component will request this Injection Mapping injection, the
* target value will be injected.
* Since this value is a plain Javascript scalar, Array or Object, it's shared in all the application and calling
* #asSingleton on such an Injection Mapping is useless.
*
* @param value
* @return {sync.InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws {Error}
*/
InjectionMapping.prototype.toValue = function (value)
{
this._sealed && this._throwSealedException();
this._toValue = value;
return this;
};
/**
* The Injection Mapping value will be resolved through a Provider function.
*
* @param {Function} injectionValueProviderFunction
* @return {sync.InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws {Error}
*/
InjectionMapping.prototype.toProvider = function (injectionValueProviderFunction)
{
this._sealed && this._throwSealedException();
this._toProvider = injectionValueProviderFunction;
return this;
};
/**
* Each time this Injection Mapping value will be requested, a new instance of the target Javascript type will
* be created.
* Use it with #asSingleton() to map a lazy-loaded shared instance to this Injection Mapping.
*
* @param {Function} javascriptType
* @return {sync.InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws {Error}
*/
InjectionMapping.prototype.toType = function (javascriptType)
{
this._sealed && this._throwSealedException();
if (!(javascriptType instanceof Function))
{
throw new Error('InjectionMapping.toType() argument must be a Javascript type (i.e. a function instantiable with "new")')
}
this._toType = javascriptType;
return this;
};
/**
* When this method is called on an Injection Mapping, its resolution will be triggered the first time it is
* requested, but any subsequent call will use this first-time resolved value.
*
* @return {sync.InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws {Error}
*/
InjectionMapping.prototype.asSingleton = function ()
{
this._sealed && this._throwSealedException();
this._asSingleton = true;
return this;
};
/**
* Resolves the injection mapping.
*
* @return the Injection Mapping resolved value
*/
InjectionMapping.prototype.resolveInjection = function ()
{
var returnedValue = null;
if (this._singletonValue) {
returnedValue = this._singletonValue;
} else if (this._toValue) {
returnedValue = this._toValue;
} else if (this._toType) {
returnedValue = new this._toType();
} else if (this._toProvider) {
// The Provider function may itself ask for other injections.
returnedValue = this._injector.triggerFunctionWithInjectedParams(this._toProvider);
}
if (this._asSingleton) {
this._singletonValue = returnedValue;//we won't ask for resolution again
}
return returnedValue;
};
/**
* Seal this Injection mapping. Any subsequent call to any of the
* #toValue, "toProvider()" or "asSingleton()" methods will throw
* an Error.
*
* @return {Object} returns a "unseal" key ; the only way to unseal this InjectionMapping it to call its "unseal()" method with this key
* @throws {Error}
* @see #unseal()
*/
InjectionMapping.prototype.seal = function ()
{
this._sealed && this._throwSealedException();
this._sealed = true;
this._sealKey = {};
return this._sealKey;
};
/**
* Reverts the effect of <code>seal</code>, makes the mapping changeable again.
*
* @param {Object} key The key to unseal the mapping. Has to be the instance returned by
* <code>seal()</code>
* @return {sync.InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws {Error} Has to be invoked with the unique key object returned by an earlier call to <code>seal</code>
* @throws {Error} Can't unseal a mapping that's not sealed
* @see #seal()
*/
InjectionMapping.prototype.unseal = function (key)
{
if (!this._sealed) {
throw new Error('Can\'t unseal a non-sealed mapping.');
}
if (key !== this._sealKey)
{
throw new InjectorError('Can\'t unseal mapping without the correct key.');
}
this._sealed = false;
this._sealKey = null;
return this;
};
/**
* If the #seal method has been called on this InjectionMapping, returns `true`
* @return {Boolean}
*/
InjectionMapping.prototype.isSealed = function ()
{
return this._sealed;
};
/**
*
* @private
*/
InjectionMapping.prototype._throwSealedException = function ()
{
throw new Error('Modifications on a sealed InjectionMapping is forbidden!');
};
// Injector
/**
* Creates a new "synchronous operations only" Injector instance.
*
* Access this class with
* <code>var Injector = require('medic-injector').InjectorSync;</code>
*
* @class sync.Injector
* @constructor
* @return {sync.Injector}
*/
var Injector = function ()
{
this._mappings = {};
return this;
};
/**
* The name of the function to trigger in a custom JS type instance after the resolution of all its Injections Points.
* @property {String}
*/
Injector.prototype.instancePostInjectionsCallbackName = 'postInjections';
/**
* Adds a new InjectionMapping to the Injector.
*
* @param {String} injectionName
* @return {sync.InjectionMapping}
*/
Injector.prototype.addMapping = function (injectionName)
{
if (!!this._mappings[injectionName]) {
throw new Error('Injection name "'+injectionName+'" is already used!');
}
var newInjectionMapping = new InjectionMapping(this, injectionName);
this._mappings[injectionName] = newInjectionMapping;
return newInjectionMapping;
};
/**
* Removes an existing InjectionMapping.
*
* @param {String} injectionName
* @return {sync.Injector}
* @throws {Error} An Error is thrown if the target InjectionMapping has been sealed
*/
Injector.prototype.removeMapping = function (injectionName)
{
if (!!this._mappings[injectionName] && this._mappings[injectionName].isSealed()) {
throw new Error('Injection name "'+injectionName+'" is sealed and cannot be removed!');
}
delete this._mappings[injectionName];
return this;
};
/**
*
* @param {String} injectionName
* @return {Boolean}
*/
Injector.prototype.hasMapping = function (injectionName)
{
return !!this._mappings[injectionName];
};
/**
*
* @param {String} injectionName
* @return {sync.InjectionMapping}
*/
Injector.prototype.getMapping = function (injectionName)
{
return this._mappings[injectionName] || null;
};
/**
* Triggers the target function with the supplied context.
* The function args are parsed, and for each of these args whose name equals a registered InjectionMapping name
* the injection will be resolved and its value will fill the matching function arg value.
*
* @param {Function} func
* @param {Object} [context=null]
* @return the function returned value
*/
Injector.prototype.triggerFunctionWithInjectedParams = function (func, context)
{
myDebug && console && console.log('triggerFunctionWithInjectedParams() ; func=', func);
var functionArgsNames = getArgumentNames(func);
var resolvedInjectionsValues = this.resolveInjections(functionArgsNames);
return func.apply(context, resolvedInjectionsValues);
};
/**
*
* @param {Object} jsTypeInstance
* @param {Boolean} [proceedToInjectionsInPostInjectionsMethodToo=false]
*/
Injector.prototype.injectInto = function (jsTypeInstance, proceedToInjectionsInPostInjectionsMethodToo)
{
// Let's scan this JS object instance for injection points...
var propsToInject = [];
for (var propName in jsTypeInstance) {
if (null === jsTypeInstance[propName] && !!this._mappings[propName]) {
// This instance property is null and its name matches a registered injection name
// --> let's handle it as an injection point!
propsToInject.push(propName);
}
}
var resolvedInjectionsValues = this.resolveInjections(propsToInject);
for (var i = 0; i < resolvedInjectionsValues.length; i++) {
var propName = propsToInject[i]
, propValue = resolvedInjectionsValues[i];
jsTypeInstance[propName] = propValue;//property injection!
}
// Okay, now we may trigger the JS object instance "postInjection" method if it has one...
if (!!jsTypeInstance[this.instancePostInjectionsCallbackName] && (jsTypeInstance[this.instancePostInjectionsCallbackName] instanceof Function)) {
if (!proceedToInjectionsInPostInjectionsMethodToo) {
// Simple "postInjection" trigger
jsTypeInstance[this.instancePostInjectionsCallbackName].apply(jsTypeInstance);
} else {
// We will look for injections in the "postInjection" method too!
this.triggerFunctionWithInjectedParams(jsTypeInstance[this.instancePostInjectionsCallbackName], jsTypeInstance);
}
}
};
/**
*
* @param {Function} jsType
* @param {Boolean} [proceedToInjectionsInPostInjectionsMethodToo=false]
* @return a new instance of the given type, with its Injection Points resolved and its "post injections" callback triggered
*/
Injector.prototype.createInjectedInstance = function (jsType, proceedToInjectionsInPostInjectionsMethodToo)
{
var newInstance = new jsType();
this.injectInto(newInstance, proceedToInjectionsInPostInjectionsMethodToo);
return newInstance;
};
/**
* Replaces all "${injectionName}" patterns in the given String with the values of the matching Injections Mappings.
* For each `null` injection mapping value, an empty string is used instead of 'null'.
*
* @param {String} str
* @return {String}
*/
Injector.prototype.parseStr = function (str)
{
var requestedInjectionsNames = [];
str.replace(/\$\{([a-z0-9_]+)\}/ig, bind(function (fullStr, injectionName) {
if (!!this._mappings[injectionName]) {
requestedInjectionsNames.push(injectionName);
}
return fullStr;//don't replace anything for the moment...
}, this));
var resolvedInjectionsValues = this.resolveInjections(requestedInjectionsNames);
for (var i = 0; i < requestedInjectionsNames.length; i++) {
var injectionName = requestedInjectionsNames[i]
, injectionValue = (null === resolvedInjectionsValues[i]) ? '' : resolvedInjectionsValues[i] ;
str = str.replace('${' + injectionName + '}', injectionValue);
}
return str;
};
/**
* Set the value of all public properties of the target JS object whose name is an injection mapping to "null".
* This lets you cancel the effect of #injectInto for clean garbage collection.
*
* @param {Object} jsTypeInstance
*/
Injector.prototype.cancelInjectionsInto = function (jsTypeInstance)
{
// Let's scan this JS object instance for injection points...
for (var propName in jsTypeInstance) {
if (!!this._mappings[propName]) {
// This instance property's name matches a registered injection name
// --> let's cancel this injection point
jsTypeInstance[propName] = null;
}
}
};
/**
*
* @param {Array} injectionsNamesArray an Array of Strings
* @return {Array} an Array of resolved Injections Mappings values
*/
Injector.prototype.resolveInjections = function (injectionsNamesArray)
{
myDebug && console && console.log('resolveInjections() ; injectionsNamesArray=', injectionsNamesArray);
var resolvedInjectionPoints = [];
for (var i = 0; i < injectionsNamesArray.length; i++ ) {
var currentInjectionName = injectionsNamesArray[i];
if (!this._mappings[currentInjectionName]) {
// We have no mapping for this arg : we'll send `null` to the function for this arg
resolvedInjectionPoints.push(null);
} else {
// We resolve the mapping
resolvedInjectionPoints.push(this._mappings[currentInjectionName].resolveInjection());
}
}
return resolvedInjectionPoints;
};
// Library export
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = Injector;
}
exports.MedicInjector = Injector;
} else if (typeof define === "function" && define.amd) {
define('medic-injector-sync', [], function () { return Injector; } );
} else {
context['MedicInjectorSync'] = Injector;
}
// Utils
// Function reflection
/**
* From Prototype library
* @see https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/function.js
*
* Prototype JavaScript framework
* (c) 2005-2010 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://www.prototypejs.org/
*
* @param {Function} fun
* @return {Array}
* @private
*/
var getArgumentNames = function (fun)
{
var names = fun.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
.replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
.replace(/\s+/g, '').split(',');
return names.length == 1 && !names[0] ? [] : names;
};
// Functions scope binding
/**
*
* @param {Function} func
* @param {Object} context
* @return {Function}
* @private
*/
var bind = function (func, context)
{
var args = Array.prototype.slice.call(arguments, 2);
return function(){
return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
};
};
})(this);
////////////////////////////////////////////////////////////////////////////////
//
// AT SOME POINT YOU NEED TO WEED ALL THIS DUPLICATED
// CAPABILITY OUT OF ENTITYINPUT.JS!
//
////////////////////////////////////////////////////////////////////////////////
textFormatter = {
'formatString' : function (string, stripTags)
{
this.string = string;
this.stripTags = stripTags ? true : false;
this.chars = this.string.split('');
this.iPoints = new Array(this.chars.length);
// this.chars.push(' ');
this.chars.push(' ');
// Remove potential html tag or entity characters
this.replaceChars('<', '<');
this.replaceChars('>', '>');
this.replaceChars('&', '&');
// This makes it possible to see the caret and selection across new lines
this.replaceChars('\n', ' \n');
// var isCommand = this.string[0] === '>';
this.renderText();
this.resolveInjections();
var displayString = this.chars.join('');
return displayString;
},
////////////////////////////////////////////////////////////////////////////////
//
// SELECTIVE RENDER FUNCTIONS
//
////////////////////////////////////////////////////////////////////////////////
'renderText' : function ()
{
this.formatTag('r');
this.formatTag('g');
this.formatTag('b');
this.formatTag('c');
this.formatTag('y');
this.formatTag('m');
this.formatTag('rh');
this.formatTag('gh');
this.formatTag('bh');
this.formatTag('ch');
this.formatTag('yh');
this.formatTag('mh');
this.formatTag('ru');
this.formatTag('gu');
this.formatTag('bu');
this.formatTag('cu');
this.formatTag('yu');
this.formatTag('mu');
this.formatTag('rb');
this.formatTag('rbh');
this.formatTag('rbu');
this.formatTag('sv');
},
////////////////////////////////////////////////////////////////////////////////
//
// PARSING & PRESENTATION FUNCTIONS
//
////////////////////////////////////////////////////////////////////////////////
'inject' : function (tag, index)
{
if (this.iPoints[index])
{
var otherTag = tag.opposite();
for (var i in this.iPoints[index])
{
if (otherTag.equals(this.iPoints[index][i]))
{
this.iPoints[index][i] = undefined;
return;
}
}
}
else
{
this.iPoints[index] = [];
}
if (tag.isOpenTag)
{
this.iPoints[index].push(tag);
}
else
{
this.iPoints[index].unshift(tag);
}
},
'resolveInjections' : function ()
{
var currentState = {
'order': []
};
var reps = this.iPoints.length;
var openTags = 0;
for (var stringIndex = 0; stringIndex <= reps; stringIndex++)
{
if (this.iPoints[stringIndex])
{
var currentTags = this.iPoints[stringIndex];
for (var currentTagIndex in currentTags)
{
var tag = currentTags[currentTagIndex];
if (!tag) continue;
if (tag.isOpenTag)
{
// We're opening a new tag.
if (currentState[tag.type] === undefined) currentState[tag.type] = 0;
// var beforeState = Boolean(currentState[tag.type]);
currentState[tag.type]++;
currentState.order.push(tag.type);
}
else
{
// We're attempting to close a tag.
if (currentState[tag.type] === undefined || currentState[tag.type] < 1)
{
// alert('Attempting to close tag that was never opened.');
continue;
}
currentState[tag.type]--;
var lastIndex = currentState.order.lastIndexOf(tag.type);
currentState.order.splice(lastIndex, 1);
}
// End of current tags
}
var tagString = ('</span>').repeat(openTags);
for (var i in currentState.order)
{
tagString = tagString + '<span class="' + currentState.order[i] + '">';
}
openTags = currentState.order.length;
// this.iPoints[stringIndex] = '</span><span class = "' + currentState.order.join(' ') + '">';
this.iPoints[stringIndex] = tagString;
}
}
for (var stringIndex = reps; stringIndex >= 0; stringIndex--)
{
if (this.iPoints[stringIndex])
{
Array.prototype.splice.call(this.chars, stringIndex, 0, this.iPoints[stringIndex]);
}
}
},
'indexesOf' : function (string)
{
var index = undefined;
var indexes = [];
do
{
index = this.string.indexOf(string, index +1);
if (index !== -1) indexes.push(index);
}
while (index !== -1)
return indexes;
},
'replaceChars' : function (char, replacement)
{
var indexes = this.indexesOf(char);
for (var i in indexes)
{
this.chars[indexes[i]] = replacement;
}
},
'tagString' : function (string, tag)
{
var indexes = this.indexesOf(string);
var offset = string.length;
var open = new Tag(tag);
var close = new Tag('/' + tag);
for (var i in indexes)
{
this.inject(open, indexes[i]);
this.inject(close, indexes[i] + offset);
}
},
'removeString' : function (string)
{
var indexes = this.indexesOf(string);
var offset = string.length;
for (var i in indexes)
{
for (var j = 0; j < offset; j++)
{
this.chars[indexes[i] + j] = '';
}
}
},
'injectBefore' : function (string, tag)
{
var indexes = this.indexesOf(string);
var iTag = new Tag(tag);
for (var i in indexes)
{
this.inject(iTag, indexes[i]);
}
},
'injectAfter' : function (string, tag)
{
var indexes = this.indexesOf(string);
var offset = string.length;
var iTag = new Tag(tag);
for (var i in indexes)
{
this.inject(iTag, indexes[i] + offset);
}
},
'formatTag' : function (tag)
{
this.injectAfter('[' + tag + ']', tag);
this.injectBefore('[/' + tag + ']', '/' + tag);
if (this.stripTags)
{
this.removeString('[' + tag + ']');
this.removeString('[/' + tag + ']');
}
else
{
this.tagString('[' + tag + ']', 'k');
this.tagString('[/' + tag + ']', 'k');
}
}
};
Accelerate Your Automation Test Cycles With LambdaTest
Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.