diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d073ed..d737329 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,19 @@
+
+# [0.2.0](https://github.com/BrainBacon/Nodep/compare/0.1.0...v0.2.0) (2015-11-11)
+
+
+### Features
+
+* **nodep.js:** add dependency tree analysis ([fcba22a](https://github.com/BrainBacon/Nodep/commit/fcba22a)), closes [#1](https://github.com/BrainBacon/Nodep/issues/1)
+* **nodep.js:** glob support for pattern matching ([762a22b](https://github.com/BrainBacon/Nodep/commit/762a22b)), closes [#2](https://github.com/BrainBacon/Nodep/issues/2)
+
+
+### BREAKING CHANGES
+
+* $p.load has now moved to $p.init
+
+
+
# [0.1.0](https://github.com/BrainBacon/Nodep/compare/0.1.0...v0.1.0) (2015-09-23)
diff --git a/README.md b/README.md
index c98ed2d..cff1cb8 100644
--- a/README.md
+++ b/README.md
@@ -35,9 +35,9 @@ $ npm install --save nodep
```js
var $p = require('nodep')();
-$p.load({
+$p.init({
myVar: localVariable
-}).load([
+}).init([
'anNpmPackage',
'./a/nested/local.dependency',
'./a.local.dependency'
@@ -59,6 +59,24 @@ module.exports = function(localDependency, myVar, anNpmPackage) {
- `./a.local.dependency` becomes `aLocalDependency` is executed and injectable
+## Glob pattern matching for directories
+### $p.init also supports glob syntax for loading multiple files from a directory or directory tree
+- Any file without a `.js` extension will be ignored
+- [glob docs and patterns](https://github.com/isaacs/node-glob)
+
+### Example
+**index.js**
+```js
+var $p = require('nodep')();
+
+$p.init('src/*').init([
+ 'anNpmPackage',
+ './a/nested/local.dependency',
+ 'glob/patterns/*'
+]);
+```
+
+
## Existing providers
Register other instances of nodep into your project.
@@ -155,13 +173,17 @@ var $p = require('nodep')();
* [.PATH_REPLACE_RESULT](#module_nodep..$p.PATH_REPLACE_RESULT) : String
* [.REMOVE_COMMENTS_REGEXP](#module_nodep..$p.REMOVE_COMMENTS_REGEXP) : RegExp
* [.REGISTER_TYPE_ERROR_MESSAGE](#module_nodep..$p.REGISTER_TYPE_ERROR_MESSAGE) : String
+ * [.CIRCULAR_DEPENDENCY_ERROR_MESSAGE](#module_nodep..$p.CIRCULAR_DEPENDENCY_ERROR_MESSAGE) : String
* [.PROVIDER_TYPE_ERROR_MESSAGE](#module_nodep..$p.PROVIDER_TYPE_ERROR_MESSAGE) : String
* [.camelCase(match, $1, offset)](#module_nodep..$p.camelCase) ⇒ String
* [.name(path)](#module_nodep..$p.name) ⇒ String
* [.args(fn)](#module_nodep..$p.args)
+ * [.applyArgs(name, args)](#module_nodep..$p.applyArgs)
* [.decorator(name, dependency, [skipInject])](#module_nodep..$p.decorator) ⇒ Object
- * [.register(path)](#module_nodep..$p.register)
- * [.load(paths)](#module_nodep..$p.load) ⇒ Object
+ * [.easyRegister(path)](#module_nodep..$p.easyRegister) ⇒ Boolean
+ * [.register(paths)](#module_nodep..$p.register)
+ * [.resolveFiles(paths)](#module_nodep..$p.resolveFiles) ⇒ Array.<String>
+ * [.init(paths)](#module_nodep..$p.init) ⇒ Object
* [.provider(instances)](#module_nodep..$p.provider) ⇒ Object
* [.inject(name)](#module_nodep..$p.inject) ⇒ ?
@@ -214,6 +236,11 @@ Expression to remove comments when parsing arguments for a dependency
### $p.REGISTER_TYPE_ERROR_MESSAGE : String
Error message to send when trying to register a non-string type
+**Kind**: static constant of [$p](#module_nodep..$p)
+
+### $p.CIRCULAR_DEPENDENCY_ERROR_MESSAGE : String
+Error message to send when a circular reference is detected in the dependency tree
+
**Kind**: static constant of [$p](#module_nodep..$p)
### $p.PROVIDER_TYPE_ERROR_MESSAGE : String
@@ -254,6 +281,17 @@ Will extract the order and name of injectable arguments in a given function
| --- | --- | --- |
| fn | function
| the function to extract injection arguments from |
+
+### $p.applyArgs(name, args)
+Function to apply args to a new dependency and register it
+
+**Kind**: static method of [$p](#module_nodep..$p)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| name | String
| the name of the new dependency to register |
+| args | Array.<String>
| the names of args to apply to the new dependeency |
+
### $p.decorator(name, dependency, [skipInject]) ⇒ Object
Main dependency injection function
@@ -282,18 +320,40 @@ Dependency Handling:
| dependency | ?
| a value to assign to this dependency |
| [skipInject] | Boolean
| inject into a provided dependency of type function unless true |
+
+### $p.easyRegister(path) ⇒ Boolean
+Easy dependency test, will register simple dependencies
+
+**Kind**: static method of [$p](#module_nodep..$p)
+**Returns**: Boolean
- true if register was successful
+
+| Param | Type | Description |
+| --- | --- | --- |
+| path | String
| the name or filepath of a dependency to register to the provider |
+
-### $p.register(path)
+### $p.register(paths)
Default registration function in front of `$p.decorator`
**Kind**: static method of [$p](#module_nodep..$p)
| Param | Type | Description |
| --- | --- | --- |
-| path | String
| the name or filepath of a dependency to register to the provider |
+| paths | String
| Array.<String>
| the name or filepath of a dependency to register to the provider or an array of the former |
+
+
+### $p.resolveFiles(paths) ⇒ Array.<String>
+Function to normalize glob type paths into file paths omitting any non-js files
+
+**Kind**: static method of [$p](#module_nodep..$p)
+**Returns**: Array.<String>
- an array with globbed paths normalized and merged with regular paths
+
+| Param | Type | Description |
+| --- | --- | --- |
+| paths | Array.<String>
| file paths and globbed paths |
-
-### $p.load(paths) ⇒ Object
+
+### $p.init(paths) ⇒ Object
Load one or more dependencies into the provider
Loading Mechanism:
- All strings in an array loaded into $p will be initialized according to `$p.register`
diff --git a/docs.hbs b/docs.hbs
index 28c7512..1f225b9 100644
--- a/docs.hbs
+++ b/docs.hbs
@@ -1,5 +1,6 @@
{{#module name="header"~}}{{>body~}}{{/module}}
-{{#module name="load"~}}{{>body~}}{{/module}}
+{{#module name="init"~}}{{>body~}}{{/module}}
+{{#module name="glob"~}}{{>body~}}{{/module}}
{{#module name="provider"~}}{{>body~}}{{/module}}
{{#module name="inject"~}}{{>body~}}{{/module}}
{{#module name="decorator"~}}{{>body~}}{{/module}}
diff --git a/nodep.js b/nodep.js
index f44cc13..5516e07 100644
--- a/nodep.js
+++ b/nodep.js
@@ -27,8 +27,10 @@
var nodep = require('./package');
var _ = require('lodash');
+var glob = require('glob');
var REGISTER_TYPE_ERROR_MESSAGE = 'Dependency is not a string';
+var CIRCULAR_DEPENDENCY_ERROR_MESSAGE = 'Circular dependency detected';
var PROVIDER_TYPE_ERROR_MESSAGE = 'Module does not have dependencies';
/**
@@ -169,6 +171,23 @@ module.exports = function() {
return output;
},
+ /**
+ * Function to apply args to a new dependency and register it
+ * @function
+ * @param {String} name the name of the new dependency to register
+ * @param {Array} args the names of args to apply to the new dependeency
+ */
+ applyArgs: function(name, dependency, args) {
+ this.dependencies[name] = dependency.apply(undefined, _.map(args, function(arg) {
+ var dep = this.dependencies[arg];
+ if(_.isUndefined(dep)) {
+ dep = module.parent.require(arg);
+ this.dependencies[arg] = dep;
+ }
+ return dep;
+ }, this));
+ },
+
/**
* ## Override existing dependencies
* ```js
@@ -208,16 +227,8 @@ module.exports = function() {
return;
}
var args = this.args(dependency);
- var self = this;
// TODO determine what to make of the "this arg" value. Perhaps set it as $p?
- this.dependencies[name] = dependency.apply(undefined, _.map(args, function(arg) {
- var dep = self.dependencies[arg];
- if(_.isUndefined(dep)) {
- dep = module.parent.require(arg);
- self.dependencies[arg] = dep;
- }
- return dep;
- }));
+ this.applyArgs(name, dependency, args);
return this;
},
@@ -229,24 +240,130 @@ module.exports = function() {
REGISTER_TYPE_ERROR_MESSAGE: REGISTER_TYPE_ERROR_MESSAGE,
/**
- * Default registration function in front of `$p.decorator`
+ * Easy dependency test, will register simple dependencies
* @function
* @param {String} path the name or filepath of a dependency to register to the provider
+ * @returns {Boolean} true if register was successful
*/
- register: function(path) {
+ easyRegister: function(path, name) {
if(!_.isString(path)) {
throw new TypeError(REGISTER_TYPE_ERROR_MESSAGE);
}
- var name = this.name(path);
+ name = name || this.name(path);
if(!_.isUndefined(this.dependencies[name])) {
- return;
+ return true;
}
if(!_.includes(path, '/')) {
this.dependencies[path] = module.parent.require(path);
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Error message to send when a circular reference is detected in the dependency tree
+ * @constant
+ * @type {String}
+ */
+ CIRCULAR_DEPENDENCY_ERROR_MESSAGE: CIRCULAR_DEPENDENCY_ERROR_MESSAGE,
+
+ /**
+ * Default registration function in front of `$p.decorator`
+ * @function
+ * @param {String|Array} paths the name or filepath of a dependency to register to the provider or an array of the former
+ */
+ register: function(paths) {
+ if(_.isArray(paths)) {
+ var index = {};
+ _.forEach(paths, function(path) {
+ if(this.easyRegister(path)) {
+ return;
+ }
+ var name = this.name(path);
+ var dep = module.parent.require(path);
+ if(!_.isFunction(dep)) {
+ this.decorator(name, dep);
+ return;
+ }
+ var args = this.args(dep);
+ if(args.length < 1) {
+ this.register(path);
+ return;
+ }
+ index[name] = module.parent.require(path);
+ index[name].args = args;
+ }, this);
+ // TODO continue here
+ var checkDep = function(name, stack) {
+ if(!_.isUndefined(this.dependencies[name])) {
+ return;
+ }
+ if(_.includes(stack, name)) {
+ throw new Error(CIRCULAR_DEPENDENCY_ERROR_MESSAGE);
+ }
+ if(_.every(_.map(index[name].args, function(arg) {
+ return !!this.dependencies[arg];
+ }, this))) {
+ this.applyArgs(name, index[name], index[name].args);
+ }
+ var childStack = _.cloneDeep(stack);
+ childStack.push(name);
+ _.forEach(index[name].args, function(arg) {
+ checkDep.apply(this, [arg, _.cloneDeep(childStack)]);
+ }, this);
+ checkDep.apply(this, [name, stack]);
+ };
+ _.forEach(index, function(dep, name) {
+ checkDep.apply(this, [name, []]);
+ }, this);
return;
}
- var dependency = module.parent.require(path);
- this.decorator(name, dependency);
+ var name = this.name(paths);
+ if(this.easyRegister(paths, name)) {
+ return;
+ }
+ this.decorator(name, module.parent.require(paths));
+ },
+
+ /**
+ * ## Glob pattern matching for directories
+ * ### $p.init also supports glob syntax for loading multiple files from a directory or directory tree
+ * - Any file without a `.js` extension will be ignored
+ * - [glob docs and patterns](https://github.com/isaacs/node-glob)
+ *
+ * ### Example
+ * **index.js**
+ * ```js
+ * var $p = require('nodep')();
+ *
+ * $p.init('src/*').init([
+ * 'anNpmPackage',
+ * './a/nested/local.dependency',
+ * 'glob/patterns/*'
+ * ]);
+ * ```
+ * @module glob
+ */
+ /**
+ * Function to normalize glob type paths into file paths omitting any non-js files
+ * @function
+ * @param {Array} paths file paths and globbed paths
+ * @returns {Array} an array with globbed paths normalized and merged with regular paths
+ */
+ resolveFiles: function(paths) {
+ return _.flattenDeep(_.map(paths, function(path) {
+ if(_.isString(path) && glob.hasMagic(path)) {
+ return _.map(_.filter(glob.sync(path, {
+ nodir: true,
+ cwd: module.parent.paths[0].substring(0, module.parent.paths[0].lastIndexOf('/'))
+ }), function(file) {
+ return file.indexOf('.js') === file.length - 3;
+ }, this), function(file) {
+ return './' + file;
+ }, this);
+ }
+ return path;
+ }, this));
},
/**
@@ -266,9 +383,9 @@ module.exports = function() {
* ```js
* var $p = require('nodep')();
*
- * $p.load({
+ * $p.init({
* myVar: localVariable
- * }).load([
+ * }).init([
* 'anNpmPackage',
* './a/nested/local.dependency',
* './a.local.dependency'
@@ -288,7 +405,7 @@ module.exports = function() {
* - `anNpmPackage` is loaded from `node_modules`
* - `myVar` is injectable
* - `./a.local.dependency` becomes `aLocalDependency` is executed and injectable
- * @module load
+ * @module init
*/
/**
* Load one or more dependencies into the provider
@@ -300,18 +417,17 @@ module.exports = function() {
* @param {(Array|Object|String)} paths a list, key/value store, or single dependency
* @returns {Object} a reference to this provider
*/
- load: function(paths) {
- var self = this;
+ init: function(paths) {
if(_.isArray(paths)) {
- _.forEach(paths, function(path) {
- self.register(path);
- });
+ this.register(this.resolveFiles(paths));
} else if(_.isObject(paths)) {
_.forEach(paths, function(path, name) {
- if(_.isUndefined(self.dependencies[name])) {
- self.dependencies[name] = path;
+ if(_.isUndefined(this.dependencies[name])) {
+ this.dependencies[name] = path;
}
- });
+ }, this);
+ } else if(_.isString(paths) && glob.hasMagic(paths)) {
+ this.init([paths]);
} else {
this.register(paths);
}
@@ -354,7 +470,6 @@ module.exports = function() {
* @returns {Object} a reference to this provider
*/
provider: function(instances) {
- var self = this;
var provide = function(instance) {
if(_.isString(instance)) {
instance = module.parent.require(instance);
@@ -365,12 +480,12 @@ module.exports = function() {
if(!instance || !instance.dependencies) {
throw new TypeError(PROVIDER_TYPE_ERROR_MESSAGE);
}
- self.load(instance.dependencies);
+ this.init(instance.dependencies);
};
if(_.isArray(instances)) {
- _.forEach(instances, provide);
+ _.forEach(instances, provide, this);
} else {
- provide(instances);
+ provide.apply(this, [instances]);
}
return this;
},
diff --git a/package.json b/package.json
index 62cebef..f18e701 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nodep",
- "version": "0.1.0",
+ "version": "0.2.0",
"description": "Node.js dependency injection",
"main": "nodep.js",
"repository": {
@@ -23,6 +23,7 @@
"author": "Brian Jesse (http://brianjesse.com)",
"license": "MIT",
"dependencies": {
+ "glob": "^5.0.15",
"lodash": "^3.10.1"
},
"devDependencies": {
diff --git a/test/mock/circular.js b/test/mock/circular.js
new file mode 100644
index 0000000..8354d23
--- /dev/null
+++ b/test/mock/circular.js
@@ -0,0 +1,2 @@
+module.exports = function(circular) {
+};
diff --git a/test/mock/provider.js b/test/mock/provider.js
index 0bb9d70..25fec58 100644
--- a/test/mock/provider.js
+++ b/test/mock/provider.js
@@ -3,7 +3,7 @@
var $p = require('../../nodep')();
module.exports = function() {
- $p.load({
+ $p.init({
foo: true
});
return $p;
diff --git a/test/mock/provider.obj.js b/test/mock/provider.obj.js
index 6a7e6b9..b09d307 100644
--- a/test/mock/provider.obj.js
+++ b/test/mock/provider.obj.js
@@ -2,7 +2,7 @@
var $p = require('../../nodep')();
-$p.load({
+$p.init({
foo: true
});
diff --git a/test/spec.js b/test/spec.js
index 038d525..5024c4e 100644
--- a/test/spec.js
+++ b/test/spec.js
@@ -1,6 +1,7 @@
'use strict';
var assert = require('assert');
+var fs = require('fs');
var _ = require('lodash');
var nodep = require('../nodep');
@@ -136,6 +137,28 @@ describe('$p.decorator', function() {
});
});
+describe('$p.easyRegister', function() {
+ beforeEach(reset);
+
+ it('should reject a non-string type', function() {
+ assert.throws($p.easyRegister, TypeError, $p.REGISTER_TYPE_ERROR_MESSAGE);
+ });
+
+ it('should find a dependency', function() {
+ $p.dependencies.bar = true;
+ assert.strictEqual($p.easyRegister('./foo', 'bar'), true);
+ });
+
+ it('should register npm dependency', function() {
+ $p.easyRegister('lodash');
+ assert.equal($p.dependencies.lodash, _);
+ });
+
+ it('should not register path', function() {
+ assert.strictEqual($p.easyRegister('./foo'), false);
+ });
+});
+
describe('$p.register', function() {
beforeEach(reset);
@@ -202,18 +225,60 @@ describe('$p.register', function() {
$p.register('./mock/args.commented');
assert.equal($p.dependencies.argsCommented, 69300);
});
+
+ it('should inject dependency array', function() {
+ $p.register([
+ './mock/no.args',
+ './mock/no.args.commented',
+ './mock/arg',
+ './mock/arg.commented',
+ './mock/args',
+ './mock/args.commented'
+ ]);
+ assert.equal($p.dependencies.argsCommented, 69300);
+ });
+
+ it('should inject dependencies in reverse', function() {
+ $p.register([
+ './mock/args.commented',
+ './mock/args',
+ './mock/arg.commented',
+ './mock/arg',
+ './mock/no.args.commented',
+ './mock/no.args',
+ ]);
+ assert.equal($p.dependencies.argsCommented, 69300);
+ });
+
+ it('should detect circular dependency', function() {
+ assert.throws(function() {
+ $p.register(['./mock/circular']);
+ }, Error, $p.CIRCULAR_DEPENDENCY_ERROR_MESSAGE);
+ });
});
-describe('$p.load', function() {
+describe('$p.resolveFiles', function() {
+ beforeEach(reset);
+
+ it('should resolve file names from directory', function() {
+ var actual = $p.resolveFiles(['mock/*']);
+ var expected = _.map(fs.readdirSync('./test/mock'), function(path) {
+ return './mock/' + path;
+ });
+ assert.strictEqual(_.difference(actual, expected).length, 0);
+ });
+});
+
+describe('$p.init', function() {
beforeEach(reset);
it('should register dependency', function() {
- $p.load('./mock/no.args');
+ $p.init('./mock/no.args');
assert.equal($p.dependencies.noArgs, 1);
});
it('should register dependency array', function() {
- $p.load([
+ $p.init([
'./mock/no.args',
'./mock/no.args.commented'
]);
@@ -222,43 +287,55 @@ describe('$p.load', function() {
});
it('should register dependency object', function() {
- $p.load({
+ $p.init({
foo: 'bar'
});
assert.equal($p.dependencies.foo, 'bar');
});
it('should not overwrite dependency from array', function() {
- $p.load(['./mock/random']);
+ $p.init(['./mock/random']);
var first = $p.dependencies.random;
- $p.load(['./mock/random']);
+ $p.init(['./mock/random']);
assert.equal($p.dependencies.random, first);
});
it('should not overwrite dependency from object', function() {
- $p.load({
+ $p.init({
foo: 'bar'
});
- $p.load({
+ $p.init({
foo: 'baz'
});
assert.equal($p.dependencies.foo, 'bar');
});
it('should return $p', function() {
- assert.equal($p.load({
+ assert.equal($p.init({
foo: 'bar'
- }).load([
+ }).init([
'./mock/num'
]), $p);
});
+
+ it('should call resolve files', function() {
+ var pattern = 'glob/*';
+ var called = false;
+ $p.resolveFiles = function(paths) {
+ assert.deepStrictEqual(paths, [pattern]);
+ called = true;
+ };
+ $p.register = function() {};
+ $p.init(pattern);
+ assert.equal(called, true);
+ });
});
describe('$p.provider', function() {
beforeEach(reset);
it('should register other provider object', function() {
- $p.provider(nodep().load({
+ $p.provider(nodep().init({
foo: true
}));
assert.equal($p.dependencies.foo, true);
@@ -266,7 +343,7 @@ describe('$p.provider', function() {
it('should register other provider function', function() {
$p.provider(function() {
- return nodep().load({
+ return nodep().init({
foo: true
});
});
@@ -275,10 +352,10 @@ describe('$p.provider', function() {
it('should register other provider array', function() {
$p.provider([
- nodep().load({
+ nodep().init({
foo: true
}),
- nodep().load({
+ nodep().init({
bar: true
})
]);