├── .gitignore ├── .bowerrc ├── .jshintrc ├── tasks ├── clean.js ├── connect.js ├── bytesize.js ├── copy.js ├── changelog.js ├── uglify.js ├── jscs.js ├── jshint.js ├── copyright.js ├── watch.js └── jasmine.js ├── src ├── .jshintrc └── diy.js ├── bower.json ├── .jscsrc ├── package.json ├── Gruntfile.js └── tests └── spec └── diy.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules/ 3 | components/ 4 | build/ 5 | 6 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "components", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": "vars", 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true 14 | } 15 | -------------------------------------------------------------------------------- /tasks/clean.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('clean', { 9 | build: ['build'] 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /tasks/connect.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('connect', { 9 | test: { 10 | port: 8000 11 | } 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": false, 7 | "noarg": true, 8 | "node": false, 9 | "sub": true, 10 | "undef": true, 11 | "devel": true, 12 | "unused": "vars", 13 | "boss": true, 14 | "eqnull": true, 15 | "browser": true, 16 | "globals": { 17 | "define": false, 18 | "require": false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tasks/bytesize.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('bytesize', { 9 | all: { 10 | src: ['build/diy.js', 'build/diy.min.js'] 11 | } 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /tasks/copy.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('copy', { 9 | build: { 10 | src: 'src/diy.js', 11 | dest: 'build/diy.js' 12 | } 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /tasks/changelog.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('changelog', { 9 | options: { 10 | from: 'source-<%= pkgReadOnly.version %>' 11 | } 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /tasks/uglify.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('uglify', { 9 | dist: { 10 | options: { 11 | sourceMap: true, 12 | sourceMapFile: 'build/diy.js.map' 13 | }, 14 | files: { 15 | 'build/diy.min.js': [ 'build/diy.js' ] 16 | } 17 | } 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /tasks/jscs.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('jscs', { 9 | src: [ 10 | '**/*.js', 11 | '!node_modules/**', 12 | '!build/**', 13 | '!components/**', 14 | '!docs/**', 15 | '!tests/**' 16 | ], 17 | options: { 18 | config: '.jscsrc' 19 | } 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /tasks/jshint.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('jshint', { 9 | config: { 10 | options: {jshintrc: '.jshintrc'}, 11 | src: ['Gruntfile.js', 'tasks/*.js', 'config/**/*.js', 'node/**/*.js'] 12 | }, 13 | app: { 14 | options: {jshintrc: 'src/.jshintrc'}, 15 | src: ['src/**/*.js'] 16 | } 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diy", 3 | "version": "0.0.0", 4 | "authors": [ 5 | "Shane Tomlinson " 6 | ], 7 | "description": "Do-It-Yourself Dependency-Injection", 8 | "moduleType": [ 9 | "amd", 10 | "globals", 11 | "node" 12 | ], 13 | "keywords": [ 14 | "dependency", 15 | "injection" 16 | ], 17 | "license": "MPL 2.0", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "components", 23 | "test", 24 | "tests" 25 | ], 26 | "devDependencies": { 27 | "requirejs": "~2.1.15", 28 | "almond": "~0.3.0", 29 | "chai": "~1.10.0", 30 | "jasmine": "~2.1.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tasks/copyright.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('copyright', { 9 | app: { 10 | options: { 11 | pattern: /This Source Code Form is subject to the terms of the Mozilla Public/ 12 | }, 13 | src: [ 14 | '**/*.js', 15 | '!tests/addons/sinon.js', 16 | '!build/**', 17 | '!components/**', 18 | '!node_modules/**', 19 | '!docs/**' 20 | ] 21 | } 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /tasks/watch.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('watch', { 9 | dev: { 10 | options: { 11 | atBegin: true 12 | }, 13 | files: ['Gruntfile.js', 'src/**/*.js', 'tests/**/*.js'], 14 | tasks: ['build', 'test'] 15 | }, 16 | debug: { 17 | options: { 18 | atBegin: true 19 | }, 20 | files: ['Gruntfile.js', 'src/**/*.js', 'tests/**/*.js'], 21 | tasks: ['test'] 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /tasks/jasmine.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | 'use strict'; 7 | 8 | grunt.config('jasmine', { 9 | dist: { 10 | src: 'src/**/*.js', 11 | options: { 12 | specs: 'tests/spec/**/*.js', 13 | host: 'http://127.0.0.1:8000/', 14 | template: require('grunt-template-jasmine-requirejs'), 15 | templateOptions: { 16 | requireConfig: { 17 | baseUrl: '.', 18 | include: ['src/diy'], 19 | paths: { 20 | chai: 'components/chai/chai' 21 | } 22 | } 23 | } 24 | } 25 | } 26 | }); 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowKeywords": ["with", "eval"], 3 | "disallowKeywordsOnNewLine": ["else"], 4 | "requireSpaceBeforeBinaryOperators": ["?", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 5 | "disallowMultipleLineStrings": true, 6 | "requireSpaceAfterBinaryOperators": ["?", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 7 | "disallowSpaceAfterObjectKeys": true, 8 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-"], 9 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 10 | "maximumLineLength": 420, 11 | "requireCapitalizedConstructors": true, 12 | "requireCurlyBraces": ["if", "else", "for", "while", "do"], 13 | "requireLineFeedAtFileEnd": true, 14 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return"], 15 | "validateIndentation": 2, 16 | "validateLineBreaks": "LF", 17 | "validateQuoteMarks": true, 18 | "validateJSDoc": { 19 | "checkParamNames": true, 20 | "checkRedundantParams": true, 21 | "requireParamTypes": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diy", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "", 13 | "license": "MPL 2.0", 14 | "devDependencies": { 15 | "bower": "1.3.12", 16 | "grunt": "0.4.5", 17 | "grunt-build-control": "git://github.com/robwierzbowski/grunt-build-control#274952", 18 | "grunt-bump": "0.0.16", 19 | "grunt-bytesize": "0.1.1", 20 | "grunt-cli": "0.1.13", 21 | "grunt-contrib-clean": "0.6.0", 22 | "grunt-contrib-requirejs": "0.4.4", 23 | "grunt-contrib-watch": "0.6.1", 24 | "grunt-conventional-changelog": "1.1.0", 25 | "grunt-copyright": "0.1.0", 26 | "grunt-jscs": "0.8.1", 27 | "load-grunt-tasks": "1.0.0", 28 | "grunt-contrib-jasmine": "~0.8.1", 29 | "grunt-template-jasmine-requirejs": "~0.2.0", 30 | "grunt-contrib-connect": "~0.9.0", 31 | "grunt-contrib-jshint": "~0.10.0", 32 | "grunt-contrib-copy": "~0.7.0", 33 | "grunt-contrib-uglify": "~0.6.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | module.exports = function (grunt) { 6 | // load all grunt tasks matching the `grunt-*` pattern 7 | /*grunt.loadNpmTasks('intern');*/ 8 | require('load-grunt-tasks')(grunt); 9 | 10 | var pkg = grunt.file.readJSON('package.json'); 11 | 12 | grunt.initConfig({ 13 | pkg: pkg, 14 | pkgReadOnly: pkg 15 | }); 16 | 17 | // load local Grunt tasks 18 | grunt.loadTasks('tasks'); 19 | 20 | grunt.registerTask('build', 21 | 'Build client', 22 | ['clean', 'lint', 'copy', 'uglify', 'bytesize']); 23 | 24 | grunt.registerTask('test', 25 | 'Run tests via PhantomJS', 26 | ['connect', 'jasmine']); 27 | 28 | grunt.registerTask('lint', 29 | 'Alias for jshint and jscs tasks', 30 | ['jshint', 'jscs']); 31 | 32 | grunt.registerTask('default', 33 | ['build']); 34 | 35 | grunt.registerTask('release', 36 | ['build', 'bump-only', 'changelog', 'bump-commit', 'yuidoc', 'buildcontrol']); 37 | 38 | grunt.registerTask('dev', 39 | ['watch:dev']); 40 | 41 | grunt.registerTask('debug', 42 | ['watch:debug']); 43 | 44 | /* 45 | grunt.registerTask('doc', 46 | 'Create client documentation using YUIDoc', 47 | ['yuidoc', 'open']); 48 | 49 | grunt.registerTask('travis', 50 | 'Test runner task for Travis CI', 51 | ['build', 'intern:node', 'intern:sauce']); 52 | */ 53 | }; 54 | -------------------------------------------------------------------------------- /src/diy.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // jscs:disable 6 | ; (function (define) { 7 | define(function(require,exports,module){ 8 | // jscs:enable 9 | 'use strict'; 10 | 11 | function DIY(depList) { 12 | this._depList = depList; 13 | for (var depName in depList) { 14 | var visited = [ depName ]; 15 | var childDeps = depList[depName].deps; 16 | checkDependencies(depList, childDeps, visited); 17 | } 18 | } 19 | 20 | DIY.prototype = { 21 | create: function (depName) { 22 | var depConfig = this._depList[depName]; 23 | if (! depConfig) { 24 | throw new Error(depName + ' is not configured'); 25 | } 26 | 27 | if (depConfig.instance) { 28 | return depConfig.instance; 29 | } 30 | 31 | var config = extend({}, depConfig.config); 32 | for (var childDepName in depConfig.deps) { 33 | config[childDepName] = this.create(depConfig.deps[childDepName]); 34 | } 35 | 36 | depConfig.instance = new depConfig.constructor(config); 37 | return depConfig.instance; 38 | } 39 | }; 40 | 41 | function extend(target) { 42 | var sources = [].slice.call(arguments, 1); 43 | 44 | sources.forEach(function (source) { 45 | for (var key in source) { 46 | target[key] = source[key]; 47 | } 48 | }); 49 | 50 | return target; 51 | } 52 | 53 | function checkDependencies(allDeps, depList, visited) { 54 | visited = visited || []; 55 | 56 | for (var depName in depList) { 57 | var depType = depList[depName]; 58 | if (visited.indexOf(depType) > -1) { 59 | throw new Error('circular dependency'); 60 | } 61 | 62 | var childDep = allDeps[depType]; 63 | if (! childDep) { 64 | throw new Error('missing configuration for ' + depType); 65 | } 66 | 67 | visited.push(depType); 68 | 69 | checkDependencies(allDeps, allDeps[depType].deps, visited); 70 | 71 | visited.pop(depType); 72 | } 73 | } 74 | 75 | module.exports = DIY; 76 | 77 | /*! 78 | * UMD/AMD/Global context Module Loader wrapper 79 | * based off https://gist.github.com/wilsonpage/8598603 80 | * 81 | * This wrapper will try to use a module loader with the 82 | * following priority: 83 | * 84 | * 1.) AMD 85 | * 2.) CommonJS 86 | * 3.) Context Variable (window in the browser) 87 | */ 88 | });})(typeof define === 'function' && define.amd ? define 89 | /*global exports, module*/ 90 | // jscs:disable 91 | : (function (name, context) { 92 | 'use strict'; 93 | return typeof module === 'object' ? function (define) { 94 | define(require, exports, module); 95 | } 96 | : function (define) { 97 | var module = { 98 | exports: {} 99 | }; 100 | var require = function (n) { 101 | return context[n]; 102 | }; 103 | 104 | define(require, module.exports, module); 105 | context[name] = module.exports; 106 | }; 107 | })('DIY', this)); 108 | // jscs:enable 109 | 110 | -------------------------------------------------------------------------------- /tests/spec/diy.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | define([ 6 | 'src/diy' 7 | ], function (DIY) { 8 | 9 | function Leaf(config) { 10 | this.field = config.field; 11 | } 12 | 13 | function Parent(config) { 14 | this.leaf1 = config.leaf1; 15 | this.leaf2 = config.leaf2; 16 | this.root_field = config.root_field; 17 | } 18 | 19 | describe('diy', function () { 20 | describe('create', function () { 21 | it('can create an object with no dependencies', function () { 22 | var depList = { 23 | leaf: { 24 | constructor: Leaf, 25 | config: { 26 | field: 'value' 27 | } 28 | } 29 | }; 30 | 31 | var diy = new DIY(depList); 32 | var leaf = diy.create('leaf'); 33 | expect(leaf instanceof Leaf).toBe(true); 34 | expect(leaf.field).toBe('value'); 35 | }); 36 | 37 | it('can create an object with multiple dependencies', function () { 38 | var depList = { 39 | leaf1: { 40 | constructor: Leaf, 41 | config: { 42 | field: 'value' 43 | } 44 | }, 45 | leaf2: { 46 | constructor: Leaf 47 | }, 48 | root: { 49 | constructor: Parent, 50 | config: { 51 | root_field: 'value' 52 | }, 53 | deps: { 54 | leaf1: 'leaf1', 55 | leaf2: 'leaf2' 56 | } 57 | } 58 | }; 59 | 60 | var diy = new DIY(depList); 61 | var root = diy.create('root'); 62 | 63 | expect(root instanceof Parent).toBe(true); 64 | expect(root.leaf1 instanceof Leaf).toBe(true); 65 | expect(root.leaf2 instanceof Leaf).toBe(true); 66 | expect(root.leaf1).not.toEqual(root.leaf2); 67 | expect(root.root_field).toEqual('value'); 68 | }); 69 | 70 | it('can re-use instantiated objects', function () { 71 | var depList = { 72 | leaf: { 73 | constructor: Leaf 74 | }, 75 | node1: { 76 | constructor: Parent, 77 | deps: { 78 | leaf: 'leaf' 79 | } 80 | }, 81 | node2: { 82 | constructor: Parent, 83 | deps: { 84 | leaf: 'leaf' 85 | } 86 | } 87 | } 88 | 89 | var diy = new DIY(depList); 90 | var node1 = diy.create('node1'); 91 | var node2 = diy.create('node2'); 92 | 93 | expect(node1.leaf).toBe(node2.leaf); 94 | }); 95 | 96 | describe('detect circular dependencies', function () { 97 | function detectCircularDeps(depList) { 98 | var err; 99 | try { 100 | var diy = new DIY(depList); 101 | } catch(e) { 102 | err = e; 103 | } finally { 104 | expect(err.message).toBe('circular dependency'); 105 | expect(diy).toBeUndefined(); 106 | } 107 | } 108 | 109 | it('errors on a node that depends on itself', function () { 110 | var depList = { 111 | root: { 112 | constructor: Parent, 113 | deps: { 114 | root: 'root' 115 | } 116 | } 117 | }; 118 | 119 | detectCircularDeps(depList); 120 | }); 121 | 122 | it('errors on binary circular-dependent nodes', function () { 123 | var depList = { 124 | node1: { 125 | constructor: Parent, 126 | deps: { 127 | root: 'node2' 128 | } 129 | }, 130 | node2: { 131 | constructor: Parent, 132 | deps: { 133 | root: 'node1' 134 | } 135 | } 136 | }; 137 | 138 | detectCircularDeps(depList); 139 | }); 140 | 141 | it('errors on a triangular circular-dependence', function () { 142 | var depList = { 143 | node1: { 144 | constructor: Parent, 145 | deps: { 146 | root: 'node2' 147 | } 148 | }, 149 | node2: { 150 | constructor: Parent, 151 | deps: { 152 | root: 'node3' 153 | } 154 | }, 155 | node3: { 156 | constructor: Parent, 157 | deps: { 158 | root: 'node1' 159 | } 160 | } 161 | }; 162 | 163 | detectCircularDeps(depList); 164 | }); 165 | }); 166 | }); 167 | }); 168 | }); 169 | 170 | --------------------------------------------------------------------------------