Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
Fixed #1
  • Loading branch information
confuser committed Jan 9, 2015
1 parent 8af613b commit 9c6c833
Show file tree
Hide file tree
Showing 37 changed files with 481 additions and 295 deletions.
25 changes: 25 additions & 0 deletions .jscsrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{ "excludeFiles":
[ "node_modules/**"
, "coverage"
]
, "requireLineFeedAtFileEnd": true
, "disallowMultipleLineBreaks": true
, "requireMultipleVarDecl": true
, "disallowEmptyBlocks": true
, "disallowSpaceAfterObjectKeys": true
, "disallowCommaBeforeLineBreak": true
, "disallowTrailingWhitespace": true
, "requireCapitalizedConstructors": true
, "requireSpacesInsideObjectBrackets": "all"
, "requireSpacesInsideArrayBrackets": "all"
, "validateLineBreaks": "LF"
, "requireSpaceBeforeBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
, "requireSpaceAfterBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
, "requireSpaceAfterKeywords": [ "if", "else", "for", "while", "do", "switch", "try", "catch" ]
, "disallowSpaceAfterPrefixUnaryOperators": [ "++", "--", "+", "-", "~", "!" ]
, "disallowSpaceBeforePostfixUnaryOperators": [ "++", "--" ]
, "requireSpaceBeforeBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
, "requireSpaceAfterBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
, "disallowKeywordsOnNewLine": [ "else" ]
, "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }
}
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
language: node_js
node_js:
- 0.10
- 0.11
234 changes: 36 additions & 198 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,229 +1,67 @@
var glob = require('glob')
, async = require('async')
, unique = require('lodash.uniq')
, serviceLocator = require('service-locator')()
, callbackNames = require('./lib/callback-names')
, pluginables = {}
, pluginNames = []
, preloadPlugins = require('./lib/preload-plugins')
, loadPlugins = require('./lib/load-plugins')
, validatePlugin = require('./lib/validate-plugin')
, beforeLoad = []
, createServiceLocator = require('service-locator')
, serviceLocator

module.exports = function (list, cb) {
serviceLocator = createServiceLocator()

// TODO Don't assume it is a glob, allow other options!
async.waterfall(
[ function (callback) {
[ function (cb) {
if (Array.isArray(list)) {
var files = []
async.each(list, function (pattern, eachCb) {
glob(pattern, function (error, found) {
if (error) return eachCb(error)
files = files.concat(found)
eachCb()
eachCb(error)
})
}, function (error) {
callback(error, files)
cb(error, files)
})
} else {
glob(list, callback)
glob(list, cb)
}
}
, handleFiles
, discoverDependencies
, validateDependencies
, handleDependencies
, load
], cb)
}

// Mainly for tests!
module.exports.reset = function () {
pluginables = {}
pluginNames = []
serviceLocator = require('service-locator')()
}

module.exports.getPlugins = function () {
return serviceLocator
}

module.exports.register = function (plugin) {
pluginNames.push(plugin.name)
pluginables[plugin.name] = plugin
}

function handleFiles(files, cb) {
async.each(files, function (file, callback) {
validatePlugin(file, function (error, plugin) {
if (error) return callback(error)

// Plugin is valid, so add it to the list
pluginables[plugin.name] = plugin
pluginNames.push(plugin.name)
, function (files, cb) {
if (!files || files.length === 0) return cb(new Error('No pluginables found'))

callback()
})
}, cb)

}

function validatePlugin(file, cb) {
var plugin = require(file)
, error = null

if (!plugin.name) {
error = new Error('No name defined in ' + file)
} else if (typeof plugin.init !== 'function') {
error = new Error('Expected an exported init function in ' + file)
}
// TODO Add more validation checks

cb(error, plugin)
}
var plugins = []

function discoverDependencies(cb) {
async.each(pluginNames, function (name, callback) {
var plugin = pluginables[name]
async.each(files, function (file, callback) {
validatePlugin(file, function (error, plugin) {
if (error) return callback(error)

if (plugin.dependencies && Array.isArray(plugin.dependencies)) {
return callback()
}

// TODO move into own file for tests
plugin.dependencies = plugin.init.toString()
.replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
.match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
.split(/,/)

if (plugin.dependencies.length === 1 && plugin.dependencies[0] === '') {
plugin.dependencies = null
}

callback()
}, cb)
}

function validateDependencies(cb) {
async.each(pluginNames, function (name, callback) {
var plugin = pluginables[name]
, error = null

if (!plugin.dependencies || plugin.dependencies.length === 0) return callback()

if (plugin.async) {
plugin.dependencies.pop()
} else if (typeof plugin.async === 'undefined') {
var lastArg = plugin.dependencies[plugin.dependencies.length - 1]

if (callbackNames.indexOf(lastArg) !== -1) {
plugin.async = true
plugin.dependencies.pop()
}
}

plugin.dependencies.forEach(function (dependency) {
if (pluginNames.indexOf(dependency) === -1) {
error = new Error(name + ' has an unknown dependency ' + dependency)
return
}
})

// pluginables[name] = plugin

callback(error)
}, cb)
}
plugins.push(plugin)

function handleDependencies(cb) {
// TODO Look into tree structure for parallel loading where possible
var loadOrder = []
, toCheck = pluginNames.slice()
, error = null

// TODO Optimise
pluginNames.forEach(function (name) {
// Load non-dependent first
var plugin = pluginables[name]

if (!plugin.dependencies || plugin.dependencies.length === 0) {
loadOrder.push(name)
toCheck.splice(toCheck.indexOf(name), 1)
}
})

// TODO Make this far more robust
toCheck.forEach(function (name) {
var plugin = pluginables[name]
loadOrder = loadOrder.concat(plugin.dependencies, name)
})

loadOrder = unique(loadOrder)

cb(error, loadOrder)

}

function load(loadOrder, cb) {
async.eachSeries(loadOrder, function (name, callback) {
var plugin = pluginables[name]

if (plugin.async) {
loadAsync(plugin, callback)
} else {
try {
loadSync(plugin)
} catch (e) {
return callback(e)
}
callback()
})
}, function (error) {
plugins = plugins.concat(beforeLoad)
beforeLoad = []

callback()
cb(error, plugins)
})
}
}, cb)
}

function getDependencyInstances(plugin) {
if (!plugin.dependencies) return []

var instances = []

plugin.dependencies.forEach(function (name) {
instances.push(serviceLocator[name])
})

return instances
}

function loadAsync(plugin, cb) {
var args = getDependencyInstances(plugin).concat(function (error, instance) {
, preloadPlugins
, loadPlugins.bind(null, serviceLocator)
], function (error, instances) {
if (error) return cb(error)

// Only register if truey
if (instance) {
serviceLocator.register(plugin.name, instance)
}

cb()
})

var error = null
serviceLocator = instances

args.forEach(function (arg) {
if (typeof arg === 'undefined') error = new Error('Undefined argument found for ' + plugin.name)
cb(null, instances)
})

if (error) return cb(error)

plugin.init.apply(null, args)
}

function loadSync(plugin) {
var args = getDependencyInstances(plugin)

args.forEach(function (arg) {
if (typeof arg === 'undefined') throw new Error('Undefined argument found for ' + plugin.name)
})

var instance = plugin.init.apply(null, args)
module.exports.getPlugins = function () {
return serviceLocator
}

// Only register if truey
if (instance) {
serviceLocator.register(plugin.name, instance)
}
module.exports.registerBeforeLoad = function (plugin) {
beforeLoad.push(plugin)
}
5 changes: 0 additions & 5 deletions lib/callback-names.js

This file was deleted.

11 changes: 11 additions & 0 deletions lib/get-dependency-instances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = function (serviceLocator, plugin) {
if (!plugin.dependencies) return []

var instances = []

plugin.dependencies.forEach(function (name) {
instances.push(serviceLocator[name])
})

return instances
}
11 changes: 11 additions & 0 deletions lib/get-function-arguments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = function (fn) {
var args = fn
.toString()
.replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
.match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
.split(/,/)

if (args.length === 1 && !args[0]) args = []

return args
}
17 changes: 17 additions & 0 deletions lib/get-function-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = function (fn) {
var name = null

if (!fn) return name

// TODO optimise
var names = fn
.toString()
.replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
.match(/^function(\w*)/)

if (!names || names.length !== 2 || !names[1]) return name

name = names[1]

return name
}
Loading

0 comments on commit 9c6c833

Please sign in to comment.