From 6035b1b2b2d26f7d6eac0fb34fc8df1b98870002 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 2 Aug 2015 16:28:49 +0100 Subject: [PATCH 1/7] feat(Package): allow event handlers to be registered --- lib/Package.js | 24 +++++++++++++++++++++++- lib/Package.spec.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/Package.js b/lib/Package.js index f6bbd03..500c579 100644 --- a/lib/Package.js +++ b/lib/Package.js @@ -1,5 +1,4 @@ var _ = require('lodash'); -var Module = require('di').Module; /** * A Dgeni Package containing processors, services and config blocks. @@ -14,6 +13,7 @@ function Package(name, dependencies) { this.dependencies = dependencies || []; this.processors = []; this.configFns = []; + this.handlers = {}; // We can't use a real di.Module here as di uses instanceof to detect module instances this.module = {}; } @@ -148,6 +148,28 @@ Package.prototype.config = function(configFn) { return this; }; +/** + * Add an event handler to this package + * @param {string} eventName The name of the event to handle + * @param {function} handlerFactory An injectable factory function that will return the handler + * @return {Package} This package for chaining + */ +Package.prototype.eventHandler = function(eventName, handlerFactory) { + + if ( typeof eventName !== 'string' ) { + throw new Error('You must provide a string identifying the type of event to handle'); + } + if (typeof handlerFactory !== 'function' ) { + throw new Error('serviceFactory must be a function.\nGot "' + typeof serviceFactory + '"'); + } + + var handlers = this.handlers[eventName] = this.handlers[eventName] || []; + var handlerName = handlerFactory.name || (this.name + '_' + eventName + '_' + handlers.length); + this.factory(handlerName, handlerFactory); + handlers.push(handlerName); + return this; +}; + Package.isPackage = function(package) { // Check the most important properties for dgeni to use a package return _.isString(package.name) && _.isArray(package.dependencies) && _.isObject(package.module); diff --git a/lib/Package.spec.js b/lib/Package.spec.js index 66c1930..9aab295 100644 --- a/lib/Package.spec.js +++ b/lib/Package.spec.js @@ -173,7 +173,49 @@ describe("Package", function() { package.config({ some: 'non-function'}); }).toThrow(); }); + }); + + describe("eventHandlers()", function() { + + it("should add eventHandler name defined by a factory function to the handlers property", function() { + var package = new Package('packageName'); + package.eventHandler('testEvent', function testHandler() {}); + expect(package.handlers['testEvent']).toEqual(['testHandler']); + }); + + it("should compute a unique name for the handler if it doesn't have one", function() { + var package = new Package('packageName'); + package.eventHandler('testEvent', function() {}); + expect(package.handlers['testEvent'][0]).toEqual('packageName_testEvent_0'); + package.eventHandler('testEvent', function() {}); + expect(package.handlers['testEvent'][1]).toEqual('packageName_testEvent_1'); + }); + + it("should complain if the eventType is not a string", function() { + var package = new Package('packageName'); + expect(function() { + package.eventHandler(function() {}); + }).toThrow(); + }); + it("should complain if the handler is not a function", function() { + var package = new Package('packageName'); + expect(function() { + package.eventHandler('testEvent', { name: 'bad handler'}); + }).toThrow(); + }); + + it("should add the eventHandler to the DI module", function() { + var package = new Package('packageName'); + var testHandler = function testHandler() {}; + package.eventHandler('testEvent', testHandler); + expect(package.module.testHandler).toEqual(['factory', testHandler]); + + + var anonymousHandler = function() {}; + package.eventHandler('testEvent', anonymousHandler); + expect(package.module.packageName_testEvent_1).toEqual(['factory', anonymousHandler]); + }); }); }); \ No newline at end of file From 3fbe2abece21fbc48cb0daf09a770892f94ed959 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 2 Aug 2015 16:28:49 +0100 Subject: [PATCH 2/7] feat(Dgeni): add event handling --- lib/Dgeni.js | 66 ++++-- lib/Dgeni.spec.js | 518 +++++++++++++++++++++++++--------------------- 2 files changed, 336 insertions(+), 248 deletions(-) diff --git a/lib/Dgeni.js b/lib/Dgeni.js index 4a2ecc7..e96e3c5 100644 --- a/lib/Dgeni.js +++ b/lib/Dgeni.js @@ -120,21 +120,35 @@ Dgeni.prototype.configureInjector = function() { }); }); - // Get the collection of processors - // We use a Map here so that we only get one of each processor name - var processors = this.processors = {}; + // Get the the processors and event handlers + var processorMap = {}; + var handlerMap = this.handlerMap = {}; packages.forEach(function(package) { + package.processors.forEach(function(processorName) { var processor = injector.get(processorName); + // Update the processor's name and package processor.name = processorName; processor.$package = package.name; // Ignore disabled processors if ( processor.$enabled !== false ) { - processors[processorName] = processor; + processorMap[processorName] = processor; } }); + + for(eventName in package.handlers) { + var handlers = handlerMap[eventName] = (handlerMap[eventName] || []); + package.handlers[eventName].forEach(function(handlerName) { + handlers.push(injector.get(handlerName)); + }); + } }); + + // Once we have configured everything sort the processors. + // This allows the config blocks to modify the $runBefore and $runAfter properties of processors. + // (Crazy idea, I know, but useful for things like debugDumpProcessor) + this.processors = sortByDependency(processorMap, '$runAfter', '$runBefore'); } return this.injector; @@ -149,19 +163,12 @@ Dgeni.prototype.generate = function() { this.configureInjector(); var dgeniConfig = this.injector.get('dgeni'); - - // Once we have configured everything sort the processors. - // This allows the config blocks to modify the $runBefore and $runAfter - // properties of processors. - // (Crazy idea, I know, but useful for things like debugDumpProcessor) - processors = sortByDependency(this.processors, '$runAfter', '$runBefore'); - var log = this.injector.get('log'); var processingPromise = Q(); var validationErrors = []; // Apply the validations on each processor - _.forEach(processors, function(processor) { + _.forEach(this.processors, function(processor) { processingPromise = processingPromise.then(function() { return validate.async(processor, processor.$validate).catch(function(errors) { validationErrors.push({ @@ -187,7 +194,7 @@ Dgeni.prototype.generate = function() { return currentDocs; }); - _.forEach(processors, function(processor) { + _.forEach(this.processors, function(processor) { if( processor.$process ) { @@ -225,6 +232,39 @@ Dgeni.prototype.generate = function() { return processingPromise; }; +/** + * Trigger a dgeni event and run all the registered handlers + * All the arguments to this call are passed through to each handler + * @param {string} eventName The event being triggered + */ +Dgeni.prototype.triggerEvent = function(eventName) { + var args = arguments; + var handlers = this.handlerMap[eventName]; + if (handlers) { + handlers.forEach(function(handler) { + handler.apply(null, args); + }); + } +}; + + +Dgeni.prototype.info = function() { + this.configureInjector(); + + var packages = sortByDependency(this.packages, 'namedDependencies'); + packages.forEach(function(pkg, index) { + log.info((index+1) + ': ' + pkg.name, '[' + pkg.dependencies.map(function(dep) { return JSON.stringify(dep.name); }).join(', ') + ']'); + }); + + var processors = sortByDependency(this.processors, '$runAfter', '$runBefore'); + log.info('== Processors (processing order) =='); + + processors.forEach(function(processor, index) { + log.info((index+1) + ': ' + processor.name, processor.$process ? '' : '(abstract)', ' from ', processor.$package); + if ( processor.description ) { log.info(' ', processor.description); } + }); +} + /** * @module Dgeni */ diff --git a/lib/Dgeni.spec.js b/lib/Dgeni.spec.js index ca9a074..c7f726f 100644 --- a/lib/Dgeni.spec.js +++ b/lib/Dgeni.spec.js @@ -27,20 +27,31 @@ describe("Dgeni", function() { }); describe("package()", function() { + it("should add the package to the packages property", function() { var testPackage = new Dgeni.Package('test-package'); dgeni.package(testPackage); expect(dgeni.packages['test-package']).toEqual(testPackage); }); + it("should create a new package if passed a string", function() { var newPackage = dgeni.package('test-package'); expect(Dgeni.Package.isPackage(newPackage)).toBeTruthy(); }); + it("should throw an error if the not passed an instance of Package or a string name", function() { expect(function() { dgeni.package({}); }).toThrow(); }); + + it("should complain if two packages have the same name", function() { + dgeni.package('test'); + expect(function() { + dgeni.package('test'); + }).toThrow(); + }); + it("should pass dependencies through to the new package", function() { var newPackage = dgeni.package('test-package', ['dep1', 'dep2']); expect(newPackage.dependencies).toEqual(['dep1', 'dep2']); @@ -100,71 +111,75 @@ describe("Dgeni", function() { expect(injector.get).toEqual(jasmine.any(Function)); }); - it("should add some basic shared services to the injector", function() { - var injector = dgeni.configureInjector(); + describe("services", function() { - expect(injector.get('dgeni')).toEqual(jasmine.any(Object)); - expect(injector.get('log')).toEqual(jasmine.any(Object)); - expect(injector.get('log').debug).toEqual(jasmine.any(Function)); - expect(injector.get('getInjectables')).toEqual(jasmine.any(Function)); - }); + it("should add some basic shared services to the injector", function() { + var injector = dgeni.configureInjector(); - it("should set stop on error defaults", function() { - var stopOnProcessingError, stopOnValidationError; - dgeni.package('testPackage').config(function(dgeni) { - stopOnProcessingError = dgeni.stopOnProcessingError; - stopOnValidationError = dgeni.stopOnValidationError; + expect(injector.get('dgeni')).toEqual(jasmine.any(Object)); + expect(injector.get('log')).toEqual(jasmine.any(Object)); + expect(injector.get('log').debug).toEqual(jasmine.any(Function)); + expect(injector.get('getInjectables')).toEqual(jasmine.any(Function)); }); - var injector = dgeni.configureInjector(); - expect(stopOnProcessingError).toBe(true); - expect(stopOnValidationError).toBe(true); - }); - }); - describe("generate()", function() { + it("should set stop on error defaults", function() { + var stopOnProcessingError, stopOnValidationError; + dgeni.package('testPackage').config(function(dgeni) { + stopOnProcessingError = dgeni.stopOnProcessingError; + stopOnValidationError = dgeni.stopOnValidationError; + }); + var injector = dgeni.configureInjector(); + expect(stopOnProcessingError).toBe(true); + expect(stopOnValidationError).toBe(true); + }); - describe("packages", function() { + it("should add services to the injector", function() { + var log = []; - it("should add services from packages in the correct package dependency order", function(done) { + dgeni.package('test-package') + .processor(function testProcessor(service1, service2) { + return { + $process: function(docs) { + log.push(service1); + log.push(service2); + } + }; + }) + .factory(function service1() { return 'service1 value'; }) + .factory(function service2(service1) { return service1 + ' service2 value'; }); + + var injector = dgeni.configureInjector(); + injector.get('testProcessor').$process(); + expect(log).toEqual(['service1 value', 'service1 value service2 value']); + }); + + it("should add services from packages in the correct package dependency order", function() { var log = []; - dgeni.package('test1', ['test2']) - .factory(function testValue() { return 'test 1'; }); - dgeni.package('test2') - .factory(function testValue() { return 'test 2'; }); - dgeni.package('test3', ['test1', 'test2']) + dgeni.package('test1', ['test2']).factory(function testValue() { return 'test 1'; }); + dgeni.package('test2').factory(function testValue() { return 'test 2'; }); + dgeni.package('test4', ['test3']) .processor(function test3Processor(testValue) { return { $process: function(docs) { - log.push(testValue); } + log.push(testValue + '(overridden)'); } }; }); - dgeni.package('test4', ['test3']) + dgeni.package('test3', ['test1', 'test2']) .processor(function test3Processor(testValue) { return { $process: function(docs) { - log.push(testValue + '(overridden)'); } + log.push(testValue); } }; }); - dgeni.generate() - .then(function() { - expect(log).toEqual(['test 1(overridden)']); - }) - .finally(done); + var injector = dgeni.configureInjector(); + injector.get('test3Processor').$process(); + expect(log).toEqual(['test 1(overridden)']); }); - - it("should complain if the two packages have the same name", function() { - dgeni.package('test'); - expect(function() { - dgeni.package('test'); - }).toThrow(); - }); - }); - describe("config blocks", function() { - it("should run the config functions in the correct package dependency order", function(done) { + it("should run the config functions in the correct package dependency order", function() { var log = []; dgeni.package('test') .processor(function testProcessor() { @@ -176,185 +191,263 @@ describe("Dgeni", function() { .config(function(testProcessor) { testProcessor.testValue = 1; }); dgeni.package('test2', ['test']) .config(function(testProcessor) { testProcessor.testValue = 2; }); - dgeni.generate() - .then(function() { - expect(log).toEqual([1]); - done(); - }); + var injector = dgeni.configureInjector(); + injector.get('testProcessor').$process(); + expect(log).toEqual([1]); }); - it("should provide access to the injector", function(done) { + it("should provide config blocks with access to the injector", function() { var localInjector; dgeni.package('test') .config(function(injector) { localInjector = injector; }); - dgeni.generate().finally(function() { - expect(localInjector.get('injector')).toBe(localInjector); - done(); + var injector = dgeni.configureInjector(); + expect(injector).toBe(localInjector); + }); + }); + + describe("eventHandlers", function() { + + it("should add eventHandlers in the correct package dependency order", function() { + function handler1() {} + function handler2() {} + function handler3() {} + function handler4() {} + + dgeni.package('test1', ['test2']).eventHandler('testEvent', function() { return handler1; }); + dgeni.package('test2') + .eventHandler('testEvent', function() { return handler2; }) + .eventHandler('testEvent2', function() { return handler3; }); + dgeni.package('test3', ['test1']).eventHandler('testEvent', function() { return handler4; }) + + dgeni.configureInjector(); + expect(dgeni.handlerMap).toEqual({ + testEvent: [handler2, handler1, handler4], + testEvent2: [handler3] }); }); }); + describe("processors", function() { - describe("services", function() { + it("should order the processors by dependency", function() { + var a = { $runAfter: ['c'], $process: function() { log.push('a'); } }; + var b = { $runAfter: ['c','e','a'], $process: function() { log.push('b'); } }; + var c = { $runBefore: ['e'], $process: function() { log.push('c'); } }; + var d = { $runAfter: ['a'], $process: function() { log.push('d'); } }; + var e = { $runAfter: [], $process: function() { log.push('e'); } }; + dgeni.package('test1') + .processor('a', a) + .processor('b', b) + .processor('c', c) + .processor('d', d) + .processor('e', e); + dgeni.configureInjector(); + expect(dgeni.processors).toEqual([c, e, a, b, d]); + }); + it("should ignore processors that have $enabled set to false", function() { + var a = { $process: function() {} }; + var b = { $enabled: false, $process: function() {} }; + var c = { $process: function() {} }; + dgeni.package('test1') + .processor('a', a) + .processor('b', b) + .processor('c', c); + dgeni.configureInjector(); + expect(dgeni.processors).toEqual([a,c]); + }); - it("should add services to the injector", function(done) { - var log = []; + it("should allow config blocks to change $enabled on a processor", function() { + var a = { $process: function() { log.push('a'); } }; + var b = { $enabled: false, $process: function() { log.push('b'); } }; + var c = { $process: function() { log.push('c'); } }; + dgeni.package('test1') + .processor('a', a) + .processor('b', b) + .processor('c', c) + .config(function(a,b,c) { + a.$enabled = false; + b.$enabled = true; + }); + dgeni.configureInjector(); + expect(dgeni.processors).toEqual([b,c]); + }); - dgeni.package('test-package') - .processor(function testProcessor(service1, service2) { - return { - $process: function(docs) { - log.push(service1); - log.push(service2); - } - }; - }) - .factory(function service1() { return 'service1 value'; }) - .factory(function service2(service1) { return service1 + ' service2 value'; }); + it("should throw an error if the $runAfter dependencies are invalid", function() { + dgeni.package('test') + .processor(function badRunAfterProcessor() { return { $runAfter: ['tags-processed'] }; }); + expect(function() { + dgeni.configureInjector();; + }).toThrowError('Missing dependency: "tags-processed" on "badRunAfterProcessor"'); + }); - dgeni.generate().finally(function() { - expect(log).toEqual(['service1 value', 'service1 value service2 value']); - done(); - }); + it("should throw an error if the $runBefore dependencies are invalid", function() { + dgeni.package('test') + .processor(function badRunBeforeProcessor() { return { $runBefore: ['tags-processed'] }; }); + expect(function() { + dgeni.configureInjector();; + }).toThrowError('Missing dependency: "tags-processed" on "badRunBeforeProcessor"'); + }); + + it("should throw an error if the processor dependencies are cyclic", function() { + dgeni.package('test') + .processor(function processor1() { return { $runBefore: ['processor2'] }; }) + .processor(function processor2() { return { $runBefore: ['processor1'] }; }); + expect(function() { + dgeni.configureInjector();; + }).toThrowError('Dependency Cycle Found: processor1 -> processor2 -> processor1'); + }); + + it("should allow config blocks to change the order of the processors", function(done) { + log = []; + dgeni.package('test') + .processor(function a() { return { $runBefore: ['b'], $process: function(docs) { log.push('a' ); } }; }) + .processor(function b() { return { $runBefore: ['c'], $process: function(docs) { log.push('b' ); } }; }) + .processor(function c() { return { $process: function(docs) { log.push('c' ); } }; }) + .config(function(a, b, c) { + b.$runBefore = []; + c.$runBefore = ['b']; + }); + dgeni.generate([]).then(function() { + expect(log).toEqual(['a', 'c', 'b']); + done(); + }); }); + }); + }); + + describe("triggerEvent()", function() { + it("should run all the specified event's handlers in the correct dependency order", function() { + var log = []; + var handler1 = function() { log.push('handler1')}; + var handler2 = function() { log.push('handler2')}; + var handler3 = function() { log.push('handler3')}; + var handler4 = function() { log.push('handler4')}; + + dgeni.package('test1', ['test2']).eventHandler('testEvent', function() { return handler1; }); + dgeni.package('test2') + .eventHandler('testEvent', function() { return handler2; }) + .eventHandler('testEvent2', function() { return handler3; }); + dgeni.package('test3', ['test1']).eventHandler('testEvent', function() { return handler4; }) + + dgeni.configureInjector(); + + dgeni.triggerEvent('testEvent2'); + expect(log).toEqual(['handler3']); + + log = []; + dgeni.triggerEvent('testEvent'); + expect(log).toEqual(['handler2', 'handler1', 'handler4']); }); - describe("processors", function() { + it("should pass through the call arguments to the handler", function() { + var handler = jasmine.createSpy('handler'); + dgeni.package('test1', []).eventHandler('testEvent', function() { return handler; }); + dgeni.configureInjector(); + dgeni.triggerEvent('testEvent', 'arg1', 'arg2', 'arg3'); + expect(handler).toHaveBeenCalledWith('testEvent', 'arg1', 'arg2', 'arg3'); + }); + }); - describe("dependencies", function() { + describe("generate()", function() { - it("should order the processors by dependency", function(done) { - var log = []; - dgeni.package('test1') - .processor(function a() { return { $runAfter: ['c'], $process: function() { log.push('a'); } }; }) - .processor(function b() { return { $runAfter: ['c','e','a'], $process: function() { log.push('b'); } }; }) - .processor(function c() { return { $runBefore: ['e'], $process: function() { log.push('c'); } }; }) - .processor(function d() { return { $runAfter: ['a'], $process: function() { log.push('d'); } }; }) - .processor(function e() { return { $runAfter: [], $process: function() { log.push('e'); } }; }); - dgeni.generate() - .then(function() { - expect(log).toEqual(['c', 'e', 'a', 'b', 'd']); - done(); - }); - }); + describe("validation", function() { - it("should ignore processors that have $enabled set to false", function(done) { - var log = []; - dgeni.package('test1') - .processor(function a() { return { $process: function() { log.push('a'); } }; }) - .processor(function b() { return { $enabled: false, $process: function() { log.push('b'); } }; }) - .processor(function c() { return { $process: function() { log.push('c'); } }; }); - dgeni.generate() - .then(function() { - expect(log).toEqual(['a', 'c']); - done(); - }); - }); + it("should fail if processor has an invalid property", function(done) { + dgeni.package('test') + .processor(function testProcessor() { + return { + $validate: { x: { presence: true } } + }; + }); - it("should allow config blocks to change $enabled on a processor", function() { - var log = []; - dgeni.package('test1') - .processor(function a() { return { $process: function() { log.push('a'); } }; }) - .processor(function b() { return { $enabled: false, $process: function() { log.push('b'); } }; }) - .processor(function c() { return { $process: function() { log.push('c'); } }; }) - .config(function(a,b,c) { - a.$enabled = false; - b.$enabled = true; - }); - dgeni.generate() - .then(function() { - expect(log).toEqual(['b', 'c']); - done(); - }); + dgeni.generate().catch(function(errors) { + expect(errors).toEqual([{ processor : "testProcessor", package : "test", errors : { x : [ "X can't be blank" ] } }]); + done(); }); + }); - it("should throw an error if the $runAfter dependencies are invalid", function() { - dgeni.package('test') - .processor(function badRunAfterProcessor() { return { $runAfter: 'tags-processed' }; }); - expect(function() { - dgeni.generate(); - }).toThrow(); - }); - it("should throw an error if the $runBefore dependencies are invalid", function() { - dgeni.package('test') - .processor(function badRunBeforeProcessor() { return { $runBefore: 'tags-processed' }; }); - expect(function() { - dgeni.generate(); - }).toThrow(); - }); + it("should not fail if all the processors properties are valid", function(done) { + var log = []; + dgeni.package('test') + .processor(function testProcessor() { + return { + $validate: { x: { presence: true } }, + $process: function() { log.push(this.x); } + }; + }) + .config(function(testProcessor) { + testProcessor.x = 'not blank'; + }); - it("should allow config blocks to change the order of the processors", function(done) { - log = []; - dgeni.package('test') - .processor(function a() { return { $runBefore: ['b'], $process: function(docs) { log.push('a' ); } }; }) - .processor(function b() { return { $runBefore: ['c'], $process: function(docs) { log.push('b' ); } }; }) - .processor(function c() { return { $process: function(docs) { log.push('c' ); } }; }) - .config(function(a, b, c) { - b.$runBefore = []; - c.$runBefore = ['b']; - }); - dgeni.generate([]).then(function() { - expect(log).toEqual(['a', 'c', 'b']); - done(); - }); + dgeni.generate().then(function() { + expect(log).toEqual(['not blank']); + done(); }); }); - describe("validation", function() { + it("should not fail if stopOnValidationError is false", function(done) { - it("should fail if processor has an invalid property", function(done) { - dgeni.package('test') - .processor(function testProcessor() { - return { - $validate: { x: { presence: true } } - }; - }); + dgeni.package('test') + .config(function(dgeni) { + dgeni.stopOnValidationError = false; + }) + .processor(function testProcessor() { + return { + $validate: { x: { presence: true } } + }; + }); - dgeni.generate().catch(function(errors) { - expect(errors).toEqual([{ processor : "testProcessor", package : "test", errors : { x : [ "X can't be blank" ] } }]); + var error; + dgeni.generate() + .catch(function(e) { + error = e; + }) + .finally(function() { + expect(error).toBeUndefined(); + expect(mockLogger.error).toHaveBeenCalled(); done(); }); - }); + }); + }); - it("should not fail if all the processors properties are valid", function(done) { - var log = []; - dgeni.package('test') - .processor(function testProcessor() { - return { - $validate: { x: { presence: true } }, - $process: function() { log.push(this.x); } - }; - }) - .config(function(testProcessor) { - testProcessor.x = 'not blank'; - }); + describe("bad-processor", function() { + var testPackage, doc, badProcessor; - dgeni.generate().then(function() { - expect(log).toEqual(['not blank']); - done(); + beforeEach(function() { + testPackage = dgeni.package('test') + .processor(function badProcessor() { + return { + $process: function() { throw new Error('processor failed'); } + }; }); + doc = {}; + }); + + describe('stopOnProcessingError', function(done) { + + it("should fail if stopOnProcessingError is true and a processor throws an Error", function(done) { + dgeni.generate() + .catch(function(e) { + expect(e).toBeDefined(); + done(); + }); }); - it("should not fail if stopOnValidationError is false", function(done) { + it("should not fail but log the error if stopOnProcessingError is false a processor throws an Error", function(done) { - dgeni.package('test') + var error; + testPackage .config(function(dgeni) { - dgeni.stopOnValidationError = false; - }) - .processor(function testProcessor() { - return { - $validate: { x: { presence: true } } - }; + dgeni.stopOnProcessingError = false; }); - var error; dgeni.generate() .catch(function(e) { error = e; @@ -366,70 +459,25 @@ describe("Dgeni", function() { }); }); - }); - - describe("bad-processor", function() { - var testPackage, doc, badProcessor; + it("should continue to process the subsequent processors after a bad-processor if stopOnProcessingError is false", function(done) { + var called = false; - beforeEach(function() { - testPackage = dgeni.package('test') - .processor(function badProcessor() { + testPackage + .config(function(dgeni) { + dgeni.stopOnProcessingError = false; + }) + .processor(function checkProcessor() { return { - $process: function() { throw new Error('processor failed'); } + $runAfter: ['badProcessor'], + $process: function() { + called = true; + } }; }); - doc = {}; - }); - - describe('stopOnProcessingError', function(done) { - - it("should fail if stopOnProcessingError is true and a processor throws an Error", function(done) { - dgeni.generate() - .catch(function(e) { - expect(e).toBeDefined(); - done(); - }); - }); - it("should not fail but log the error if stopOnProcessingError is false a processor throws an Error", function(done) { - - var error; - testPackage - .config(function(dgeni) { - dgeni.stopOnProcessingError = false; - }); - - dgeni.generate() - .catch(function(e) { - error = e; - }) - .finally(function() { - expect(error).toBeUndefined(); - expect(mockLogger.error).toHaveBeenCalled(); - done(); - }); - }); - - it("should continue to process the subsequent processors after a bad-processor if stopOnProcessingError is false", function(done) { - var called = false; - - testPackage - .config(function(dgeni) { - dgeni.stopOnProcessingError = false; - }) - .processor(function checkProcessor() { - return { - $runAfter: ['badProcessor'], - $process: function() { - called = true; - } - }; - }); - - dgeni.generate().finally(function() { - expect(called).toEqual(true); - done(); - }); + dgeni.generate().finally(function() { + expect(called).toEqual(true); + done(); }); }); }); From ae5dee6e4af7d32577e6a058dd46b9f27d9bb80c Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 23 Aug 2015 17:15:10 -0400 Subject: [PATCH 3/7] refact(Dgeni): move processor validation into a package --- lib/Dgeni.js | 89 ++++++------ lib/Dgeni.spec.js | 127 +++++++----------- lib/legacyPackages/processorValidation.js | 40 ++++++ .../processorValidation.spec.js | 85 ++++++++++++ 4 files changed, 217 insertions(+), 124 deletions(-) create mode 100644 lib/legacyPackages/processorValidation.js create mode 100644 lib/legacyPackages/processorValidation.spec.js diff --git a/lib/Dgeni.js b/lib/Dgeni.js index e96e3c5..c8cafd9 100644 --- a/lib/Dgeni.js +++ b/lib/Dgeni.js @@ -2,8 +2,8 @@ var _ = require('lodash'); var di = require('di'); var Package = require('./Package'); var sortByDependency = require('./util/dependency-sort'); -var validate = require('validate.js'); var Q = require('q'); +var validationPackage = require('./legacyPackages/processorValidation'); /** * Create an instance of the Dgeni documentation generator, loading any packages passed in as a @@ -15,6 +15,10 @@ function Dgeni(packages) { packages = packages || []; if ( !Array.isArray(packages) ) { throw new Error('packages must be an array'); } + + // Add in the legacy validation that was originally part of the core Dgeni tool. + this.package(validationPackage); + _.map(packages, this.package, this); } @@ -32,6 +36,8 @@ Dgeni.Package = Package; */ Dgeni.prototype.package = function(package, dependencies) { + if ( this.injector) { throw new Error('injector already configured - you cannot add a new package'); } + if ( typeof package === 'string' ) { package = new Package(package, dependencies); } if ( !(Package.isPackage(package)) ) { throw new Error('package must be an instance of Package'); } if ( this.packages[package.name] ) { @@ -89,19 +95,18 @@ Dgeni.prototype.package = function(package, dependencies) { */ Dgeni.prototype.configureInjector = function() { - if ( !this.injector ) { + var dgeni = this; + + if ( !dgeni.injector ) { // Sort the packages by their dependency - ensures that services and configs are loaded in the // correct order - var packages = sortByDependency(this.packages, 'namedDependencies'); + var packages = dgeni.packages = sortByDependency(dgeni.packages, 'namedDependencies'); // Create a module containing basic shared services - var dgeniConfig = { - stopOnValidationError: true, - stopOnProcessingError: true - }; + dgeni.stopOnProcessingError = true; var dgeniModule = new di.Module() - .value('dgeni', dgeniConfig) + .value('dgeni', dgeni) .factory('log', require('./util/log')) .factory('getInjectables', require('./util/getInjectables')); @@ -110,8 +115,7 @@ Dgeni.prototype.configureInjector = function() { modules.unshift(dgeniModule); // Create the injector and - var injector = this.injector = new di.Injector(modules); - + var injector = dgeni.injector = new di.Injector(modules); // Apply the config blocks packages.forEach(function(package) { @@ -122,7 +126,7 @@ Dgeni.prototype.configureInjector = function() { // Get the the processors and event handlers var processorMap = {}; - var handlerMap = this.handlerMap = {}; + dgeni.handlerMap = {}; packages.forEach(function(package) { package.processors.forEach(function(processorName) { @@ -138,7 +142,7 @@ Dgeni.prototype.configureInjector = function() { }); for(eventName in package.handlers) { - var handlers = handlerMap[eventName] = (handlerMap[eventName] || []); + var handlers = dgeni.handlerMap[eventName] = (dgeni.handlerMap[eventName] || []); package.handlers[eventName].forEach(function(handlerName) { handlers.push(injector.get(handlerName)); }); @@ -148,10 +152,10 @@ Dgeni.prototype.configureInjector = function() { // Once we have configured everything sort the processors. // This allows the config blocks to modify the $runBefore and $runAfter properties of processors. // (Crazy idea, I know, but useful for things like debugDumpProcessor) - this.processors = sortByDependency(processorMap, '$runAfter', '$runBefore'); + dgeni.processors = sortByDependency(processorMap, '$runAfter', '$runBefore'); } - return this.injector; + return dgeni.injector; }; /** @@ -159,34 +163,11 @@ Dgeni.prototype.configureInjector = function() { * @return {Promise} A promise to the generated documents */ Dgeni.prototype.generate = function() { + var dgeni = this; + var injector = this.configureInjector(); + var log = injector.get('log'); - this.configureInjector(); - - var dgeniConfig = this.injector.get('dgeni'); - var log = this.injector.get('log'); - var processingPromise = Q(); - var validationErrors = []; - - // Apply the validations on each processor - _.forEach(this.processors, function(processor) { - processingPromise = processingPromise.then(function() { - return validate.async(processor, processor.$validate).catch(function(errors) { - validationErrors.push({ - processor: processor.name, - package: processor.$package, - errors: errors - }); - log.error('Invalid property in "' + processor.name + '" (in "' + processor.$package + '" package)'); - log.error(errors); - }); - }); - }); - - processingPromise = processingPromise.then(function() { - if ( validationErrors.length > 0 && dgeniConfig.stopOnValidationError ) { - return Q.reject(validationErrors); - } - }); + var processingPromise = this.triggerEvent('generationStart'); // Process the docs var currentDocs = []; @@ -214,7 +195,7 @@ Dgeni.prototype.generate = function() { log.error(error.stack); } - if ( dgeniConfig.stopOnProcessingError ) { return Q.reject(error); } + if ( dgeni.stopOnProcessingError ) { return Q.reject(error); } return currentDocs; }); @@ -236,35 +217,47 @@ Dgeni.prototype.generate = function() { * Trigger a dgeni event and run all the registered handlers * All the arguments to this call are passed through to each handler * @param {string} eventName The event being triggered + * @return {Promise} A promise to an array of the results from each of the handlers */ Dgeni.prototype.triggerEvent = function(eventName) { var args = arguments; var handlers = this.handlerMap[eventName]; + var handlersPromise = Q(); + var results = []; if (handlers) { handlers.forEach(function(handler) { - handler.apply(null, args); + handlersPromise = handlersPromise.then(function() { + var handlerPromise = Q(handler.apply(null, args)); + handlerPromise.then(function(result) { + results.push(result); + }); + return handlerPromise; + }); }); } + return handlersPromise.then(function() { + return results; + }); }; Dgeni.prototype.info = function() { - this.configureInjector(); + var injector = this.configureInjector(); + var log = injector.get('log'); - var packages = sortByDependency(this.packages, 'namedDependencies'); - packages.forEach(function(pkg, index) { + this.packages.forEach(function(pkg, index) { log.info((index+1) + ': ' + pkg.name, '[' + pkg.dependencies.map(function(dep) { return JSON.stringify(dep.name); }).join(', ') + ']'); }); - var processors = sortByDependency(this.processors, '$runAfter', '$runBefore'); log.info('== Processors (processing order) =='); - processors.forEach(function(processor, index) { + this.processors.forEach(function(processor, index) { log.info((index+1) + ': ' + processor.name, processor.$process ? '' : '(abstract)', ' from ', processor.$package); if ( processor.description ) { log.info(' ', processor.description); } }); } + /** * @module Dgeni */ diff --git a/lib/Dgeni.spec.js b/lib/Dgeni.spec.js index c7f726f..da31a29 100644 --- a/lib/Dgeni.spec.js +++ b/lib/Dgeni.spec.js @@ -1,4 +1,5 @@ var Dgeni = require('./Dgeni'); +var Q = require('q'); describe("Dgeni", function() { var dgeni, mockLogger; @@ -123,14 +124,12 @@ describe("Dgeni", function() { }); it("should set stop on error defaults", function() { - var stopOnProcessingError, stopOnValidationError; + var stopOnProcessingError; dgeni.package('testPackage').config(function(dgeni) { stopOnProcessingError = dgeni.stopOnProcessingError; - stopOnValidationError = dgeni.stopOnValidationError; }); var injector = dgeni.configureInjector(); expect(stopOnProcessingError).toBe(true); - expect(stopOnValidationError).toBe(true); }); it("should add services to the injector", function() { @@ -222,9 +221,26 @@ describe("Dgeni", function() { dgeni.package('test3', ['test1']).eventHandler('testEvent', function() { return handler4; }) dgeni.configureInjector(); - expect(dgeni.handlerMap).toEqual({ + expect(dgeni.handlerMap).toEqual(jasmine.objectContaining({ testEvent: [handler2, handler1, handler4], testEvent2: [handler3] + })); + }); + }); + + describe("legacy validation", function() { + + it("should fail if processor has an invalid property", function(done) { + dgeni.package('test') + .processor(function testProcessor() { + return { + $validate: { x: { presence: true } } + }; + }); + + dgeni.generate().catch(function(errors) { + expect(errors).toEqual([{ processor : "testProcessor", package : "test", errors : { x : [ "X can't be blank" ] } }]); + done(); }); }); }); @@ -320,7 +336,7 @@ describe("Dgeni", function() { describe("triggerEvent()", function() { - it("should run all the specified event's handlers in the correct dependency order", function() { + it("should run all the specified event's handlers in the correct dependency order", function(done) { var log = []; var handler1 = function() { log.push('handler1')}; var handler2 = function() { log.push('handler2')}; @@ -335,87 +351,46 @@ describe("Dgeni", function() { dgeni.configureInjector(); - dgeni.triggerEvent('testEvent2'); - expect(log).toEqual(['handler3']); - - log = []; - dgeni.triggerEvent('testEvent'); - expect(log).toEqual(['handler2', 'handler1', 'handler4']); + dgeni.triggerEvent('testEvent').finally(function() { + expect(log).toEqual(['handler2', 'handler1', 'handler4']); + done(); + }); }); - - it("should pass through the call arguments to the handler", function() { + it("should pass through the call arguments to the handler", function(done) { var handler = jasmine.createSpy('handler'); dgeni.package('test1', []).eventHandler('testEvent', function() { return handler; }); dgeni.configureInjector(); - dgeni.triggerEvent('testEvent', 'arg1', 'arg2', 'arg3'); - expect(handler).toHaveBeenCalledWith('testEvent', 'arg1', 'arg2', 'arg3'); - }); - }); - - describe("generate()", function() { - - describe("validation", function() { - - it("should fail if processor has an invalid property", function(done) { - dgeni.package('test') - .processor(function testProcessor() { - return { - $validate: { x: { presence: true } } - }; - }); - - dgeni.generate().catch(function(errors) { - expect(errors).toEqual([{ processor : "testProcessor", package : "test", errors : { x : [ "X can't be blank" ] } }]); - done(); - }); - }); - - - it("should not fail if all the processors properties are valid", function(done) { - var log = []; - dgeni.package('test') - .processor(function testProcessor() { - return { - $validate: { x: { presence: true } }, - $process: function() { log.push(this.x); } - }; - }) - .config(function(testProcessor) { - testProcessor.x = 'not blank'; - }); - - dgeni.generate().then(function() { - expect(log).toEqual(['not blank']); - done(); - }); + dgeni.triggerEvent('testEvent', 'arg1', 'arg2', 'arg3').finally(function() { + expect(handler).toHaveBeenCalledWith('testEvent', 'arg1', 'arg2', 'arg3'); + done(); }); + }); - it("should not fail if stopOnValidationError is false", function(done) { + it('should return a promise to event handler results', function(done) { + function handler1() { } + function handler2() { return true; } + function handler3() { return { message: 'info' }; } + function handler4() { return Q(); } + function handler5() { return Q(true); } + function handler6() { return Q({ message: 'info async'}); } - dgeni.package('test') - .config(function(dgeni) { - dgeni.stopOnValidationError = false; - }) - .processor(function testProcessor() { - return { - $validate: { x: { presence: true } } - }; - }); - - var error; - dgeni.generate() - .catch(function(e) { - error = e; - }) - .finally(function() { - expect(error).toBeUndefined(); - expect(mockLogger.error).toHaveBeenCalled(); - done(); - }); + dgeni.package('test1', []) + .eventHandler('testEvent', function() { return handler1; }) + .eventHandler('testEvent', function() { return handler2; }) + .eventHandler('testEvent', function() { return handler3; }) + .eventHandler('testEvent', function() { return handler4; }) + .eventHandler('testEvent', function() { return handler5; }) + .eventHandler('testEvent', function() { return handler6; }); + dgeni.configureInjector(); + dgeni.triggerEvent('testEvent').then(function(results) { + expect(results).toEqual([undefined, true, {message:'info'}, undefined, true, {message:'info async'}]); + done(); }); - }); + }); + + describe("generate()", function() { describe("bad-processor", function() { var testPackage, doc, badProcessor; diff --git a/lib/legacyPackages/processorValidation.js b/lib/legacyPackages/processorValidation.js new file mode 100644 index 0000000..290843d --- /dev/null +++ b/lib/legacyPackages/processorValidation.js @@ -0,0 +1,40 @@ +var Package = require('../Package'); +var Q = require('q'); +var validate = require('validate.js'); + +module.exports = new Package('processorValidation') + +.config(function(dgeni) { + dgeni.stopOnValidationError = true; +}) + +.eventHandler('generationStart', function validateProcessors(log, dgeni) { + return function validateProcessorsImpl() { + var validationErrors = []; + + var validationPromise = Q(); + + // Apply the validations on each processor + dgeni.processors.forEach(function(processor) { + validationPromise = validationPromise.then(function() { + return validate.async(processor, processor.$validate).catch(function(errors) { + validationErrors.push({ + processor: processor.name, + package: processor.$package, + errors: errors + }); + log.error('Invalid property in "' + processor.name + '" (in "' + processor.$package + '" package)'); + log.error(errors); + }); + }); + }); + + validationPromise = validationPromise.then(function() { + if ( validationErrors.length > 0 && dgeni.stopOnValidationError ) { + return Q.reject(validationErrors); + } + }); + + return validationPromise; + }; +}); \ No newline at end of file diff --git a/lib/legacyPackages/processorValidation.spec.js b/lib/legacyPackages/processorValidation.spec.js new file mode 100644 index 0000000..28d2c43 --- /dev/null +++ b/lib/legacyPackages/processorValidation.spec.js @@ -0,0 +1,85 @@ +var Dgeni = require('../Dgeni'); +var processorValidationPackage = require('./processorValidation'); + +describe("processorValidation", function() { + + var dgeni, mockLogger; + + beforeEach(function() { + mockLogger = jasmine.createSpyObj('log', ['error', 'warning', 'info', 'debug', 'silly']); + dgeni = new Dgeni(); + var mockLoggerPackage = dgeni.package('mockLogger'); + mockLoggerPackage.factory(function log() { return mockLogger; }); + }); + + it("should set stop on error defaults", function() { + var stopOnProcessingError, stopOnValidationError; + dgeni.package('testPackage', [processorValidationPackage]) + .config(function(dgeni) { + stopOnProcessingError = dgeni.stopOnProcessingError; + stopOnValidationError = dgeni.stopOnValidationError; + }); + var injector = dgeni.configureInjector(); + expect(stopOnProcessingError).toBe(true); + expect(stopOnValidationError).toBe(true); + }); + + + it("should fail if processor has an invalid property", function(done) { + dgeni.package('testPackage', [processorValidationPackage]) + .processor(function testProcessor() { + return { + $validate: { x: { presence: true } } + }; + }); + + dgeni.generate().catch(function(errors) { + expect(errors).toEqual([{ processor : "testProcessor", package : "testPackage", errors : { x : [ "X can't be blank" ] } }]); + done(); + }); + }); + + + it("should not fail if all the processors properties are valid", function(done) { + var log = []; + dgeni.package('testPackage', [processorValidationPackage]) + .processor(function testProcessor() { + return { + $validate: { x: { presence: true } }, + $process: function() { log.push(this.x); } + }; + }) + .config(function(testProcessor) { + testProcessor.x = 'not blank'; + }); + + dgeni.generate().then(function() { + expect(log).toEqual(['not blank']); + done(); + }); + }); + + it("should not fail if stopOnValidationError is false", function(done) { + dgeni.package('testPackage', [processorValidationPackage]) + .config(function(dgeni) { + dgeni.stopOnValidationError = false; + }) + .processor(function testProcessor() { + return { + $validate: { x: { presence: true } } + }; + }); + + var error; + dgeni.generate() + .catch(function(e) { + error = e; + }) + .finally(function() { + expect(error).toBeUndefined(); + expect(mockLogger.error).toHaveBeenCalled(); + done(); + }); + }); + +}); \ No newline at end of file From ec27f476347e8a18ec8eb2f651c21cc0e0219cb9 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 23 Aug 2015 17:54:37 -0400 Subject: [PATCH 4/7] refact(Dgeni): use `dgeni` rather than `this` to clean up the code --- lib/Dgeni.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Dgeni.js b/lib/Dgeni.js index c8cafd9..42c173d 100644 --- a/lib/Dgeni.js +++ b/lib/Dgeni.js @@ -164,10 +164,10 @@ Dgeni.prototype.configureInjector = function() { */ Dgeni.prototype.generate = function() { var dgeni = this; - var injector = this.configureInjector(); + var injector = dgeni.configureInjector(); var log = injector.get('log'); - var processingPromise = this.triggerEvent('generationStart'); + var processingPromise = dgeni.triggerEvent('generationStart'); // Process the docs var currentDocs = []; @@ -175,7 +175,7 @@ Dgeni.prototype.generate = function() { return currentDocs; }); - _.forEach(this.processors, function(processor) { + _.forEach(dgeni.processors, function(processor) { if( processor.$process ) { From 19cc779a11c7f7ac39de8cfabe58009b40fecf84 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 23 Aug 2015 17:15:10 -0400 Subject: [PATCH 5/7] feat(Dgeni): add processorStart and processorComplete events --- lib/Dgeni.js | 19 ++++++++++++++++--- lib/Dgeni.spec.js | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/Dgeni.js b/lib/Dgeni.js index 42c173d..b10427b 100644 --- a/lib/Dgeni.js +++ b/lib/Dgeni.js @@ -181,14 +181,27 @@ Dgeni.prototype.generate = function() { processingPromise = processingPromise.then(function(docs) { currentDocs = docs; - log.info('running processor:', processor.name); - return Q(currentDocs).then(function() { + return Q() + + .then(function() { + log.info('running processor:', processor.name); + return dgeni.triggerEvent('processorStart', processor, docs); + }) + + .then(function() { // We need to wrap this $process call in a new promise handler so that we can catch // errors triggered by exceptions thrown in the $process method // before they reach the processingPromise handlers return processor.$process(docs) || docs; - }).catch(function(error) { + }) + + .then(function(docs) { + return dgeni.triggerEvent('processorEnd', processor, docs) + .then(function() { return docs; }); + }) + + .catch(function(error) { error.message = 'Error running processor "' + processor.name + '":\n' + error.message; if ( error.stack ) { diff --git a/lib/Dgeni.spec.js b/lib/Dgeni.spec.js index da31a29..728bc34 100644 --- a/lib/Dgeni.spec.js +++ b/lib/Dgeni.spec.js @@ -73,7 +73,7 @@ describe("Dgeni", function() { }); }); - it("should not load a dependency that is already loaded", function() { + it("should not load a dependency that is already loaded", function(done) { var log = []; // Load package a1, with name 'a' From 795174687bcb1e21c01d785c8617c09fbf1a76fb Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 23 Aug 2015 18:11:59 -0400 Subject: [PATCH 6/7] refact(Dgeni): move execution of each processor into a new method --- lib/Dgeni.js | 78 +++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/lib/Dgeni.js b/lib/Dgeni.js index b10427b..df260fa 100644 --- a/lib/Dgeni.js +++ b/lib/Dgeni.js @@ -177,53 +177,58 @@ Dgeni.prototype.generate = function() { _.forEach(dgeni.processors, function(processor) { - if( processor.$process ) { + processingPromise = processingPromise.then(function(docs) { + return dgeni.runProcessor(processor, docs); + }); + }); - processingPromise = processingPromise.then(function(docs) { - currentDocs = docs; + processingPromise.catch(function(error) { + log.error('Error processing docs: ', error.stack || error.message || error ); + }) - return Q() + return processingPromise.then(function(docs) { + dgeni.triggerEvent('generationEnd'); + return docs; + }); +}; - .then(function() { - log.info('running processor:', processor.name); - return dgeni.triggerEvent('processorStart', processor, docs); - }) - .then(function() { - // We need to wrap this $process call in a new promise handler so that we can catch - // errors triggered by exceptions thrown in the $process method - // before they reach the processingPromise handlers - return processor.$process(docs) || docs; - }) +Dgeni.prototype.runProcessor = function(processor, docs) { + var dgeni = this; + var log = dgeni.injector.get('log'); + var promise = Q(docs); - .then(function(docs) { - return dgeni.triggerEvent('processorEnd', processor, docs) - .then(function() { return docs; }); - }) + if( !processor.$process ) { + return promise; + } - .catch(function(error) { + return promise - error.message = 'Error running processor "' + processor.name + '":\n' + error.message; - if ( error.stack ) { - log.error(error.stack); - } + .then(function() { + log.info('running processor:', processor.name); + return dgeni.triggerProcessorEvent('processorStart', processor, docs); + }) - if ( dgeni.stopOnProcessingError ) { return Q.reject(error); } + .then(function(docs) { + // We need to wrap this $process call in a new promise handler so that we can catch + // errors triggered by exceptions thrown in the $process method + // before they reach the promise handlers + return processor.$process(docs) || docs; + }) - return currentDocs; - }); - }); + .then(function(docs) { + return dgeni.triggerProcessorEvent('processorEnd', processor, docs); + }) - } + .catch(function(error) { - return currentDocs; - }); + error.message = 'Error running processor "' + processor.name + '":\n' + error.message; + log.error(error.stack || error.message); - processingPromise.catch(function(error) { - log.error('Error processing docs: ', error ); - }); + if ( dgeni.stopOnProcessingError ) { return Q.reject(error); } - return processingPromise; + return docs; + }); }; /** @@ -254,6 +259,11 @@ Dgeni.prototype.triggerEvent = function(eventName) { }; +Dgeni.prototype.triggerProcessorEvent = function(eventName, processor, docs) { + return this.triggerEvent(eventName, processor, docs).then(function() { return docs; }); +}; + + Dgeni.prototype.info = function() { var injector = this.configureInjector(); var log = injector.get('log'); From 7bc7315c47f0df383a731d9f8710f731b621d8fd Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 23 Aug 2015 20:13:57 -0400 Subject: [PATCH 7/7] docs(README): add info about event triggering and handling --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index e276cce..f70c519 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,10 @@ how to validate the configuration of the Processor. to validate the properties of this Processor. +** Note that the validation feature has been moved to its own Dgeni Package `processorValidation`. +Currently dgeni automatically adds this new package to a new instance of dgeni so that is still available +for backward compatibility. In a future release this package will be moved to `dgeni-packages`.** + ### Defining a Processor You define Processors just like you would a Service: @@ -334,6 +338,60 @@ myPackage.config(function(readFilesProcessor) { }); ``` +## Dgeni Events + +In Dgeni you can trigger and handle **events** to allow packages to take part in the processing +lifecycle of the documentation generation. + + +### Triggering Events + +You trigger an event simply by calling `triggerEvent(eventName, ...)` on a `Dgeni` instance. + +The `eventName` is a string that identifies the event to be triggered, which is used to wire up +event handlers. Additional arguments are passed through to the handlers. + +Each handler that is registered for the event is called in series. The return value +from the call is a promise to the event being handled. This allows event handlers to be async. +If any handler returns a rejected promise the event triggering is cancelled and the rejected +promise is returned. + +For example: + +```js +var eventPromise = dgeni.triggerEvent('someEventName', someArg, otherArg); +``` + +### Handling Events + +You register an event handler in a `Package`, by calling `handleEvent(eventName, handlerFactory)` on +the package instance. The handlerFactory will be used by the DI system to get the handler, which allows +you to inject services to be available to the handler. + +The handler factory should return the handler function. This function will receive all the arguments passed +to the `triggerHandler` method. As a minimum this will include the `eventName`. + +For example: + +```js +myPackage.eventHandler('generationStart', function validateProcessors(log, dgeni) { + return function validateProcessorsImpl(eventName) { + ... + }; +}); + +``` + +### Built-in Events + +Dgeni itself triggers the following events during documentation generation: + +* `generationStart`: triggered after the injector has been configured and before the processors begin + their work. +* `generationEnd`: triggered after the processors have all completed their work successfully. +* `processorStart`: triggered just before the call to `$process`, for each processor. +* `processorEnd`: triggered just after `$process` has completed successfully, for each processor. + ## License Apache 2