diff --git a/index.js b/index.js index a7b1846..d7c0613 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ var d = require('dejavu'), inter = require('./lib/string/cast-interpolate'), validate = require('./lib/validate_task'), Logger = require('./lib/Logger'), + TaskBuilder = require('./lib/TaskBuilder'), GruntRunner = require('./lib/grunt/Runner') ; @@ -48,11 +49,12 @@ var Automaton = d.Class.declare({ * If the task already exists, it will be replaced. * The task will be validaded only when its run. * - * @param {Object} task The task definition + * @param {Object|Function} task The task definition * * @return {Automaton} Chainable! */ addTask: function (task) { + task = this._getTaskObject(task); validate(task); assert(task.id, 'Can only add tasks with an id'); @@ -200,6 +202,29 @@ var Automaton = d.Class.declare({ return stream; }, + /** + * Get the task object in case the task is a function that will build the object. + * + * @param {Object|Function} task The task object or the builder + * + * @return {Object} The task + */ + _getTaskObject: function (task) { + var builder; + + if (utils.lang.isFunction(task)) { + builder = new TaskBuilder(); + try { + task(builder); + } catch (e) { + throw new Error('Unable to get task from builder: ' + e.message); + } + task = builder.toObject(); + } + + return task; + }, + /** * Create a batch for a task. * The batch is a sequence of functions that form the task. @@ -220,9 +245,15 @@ var Automaton = d.Class.declare({ // grab its real definition this._assertTaskLoaded(def.task); def.task = this.getTask(def.task); - // otherwise, trigger validation if is the root task - } else if (def.depth === 1) { - validate(def.task); + } else { + // if task is a function then needs a builder + if (utils.lang.isFunction(def.task)) { + def.task = this._getTaskObject(def.task); + } + // trigger validation if is the root task + if (def.depth === 1) { + validate(def.task); + } } // fill in the options with default values where the option was not provided @@ -271,12 +302,12 @@ var Automaton = d.Class.declare({ def.task.tasks.forEach(function (subtask) { var subtaskDef = this._createTaskDefinition(subtask, def); - // if it's a function - if (utils.lang.isFunction(subtaskDef.task)) { - batch.push(this._batchFunctionTask(subtaskDef)); // if it's a grunt task - } else if (subtaskDef.grunt) { + if (subtaskDef.grunt) { batch.push(this._batchGruntTask(subtaskDef)); + // if it's a inline function + } else if (utils.lang.isFunction(subtaskDef.task) && subtaskDef.task.length !== 1) { + batch.push(this._batchFunctionTask(subtaskDef)); // then it must be another task } else { batch.push(this._batchTask(subtaskDef)); @@ -298,7 +329,7 @@ var Automaton = d.Class.declare({ } }.$bind(this); - // return a final function with everything set it + // return a final function which calls everything in order return function (next) { // run pre-task preTaskFunc(function (err, disabled) { @@ -307,14 +338,14 @@ var Automaton = d.Class.declare({ return next(); } - // if there was an error, run pos-task afterwards + // if there was an error in the pre-task, run pos-task immediately if (err) { return posTaskFunc(err, next); } // run each subtask async.series(batch, function (err) { - // finally run the pos-task + // finally run the pos-task, even if there was an error posTaskFunc(err, next); }); }); diff --git a/test/helpers/tasks/callback-builder.js b/test/helpers/tasks/callback-builder.js new file mode 100644 index 0000000..c8f8f9f --- /dev/null +++ b/test/helpers/tasks/callback-builder.js @@ -0,0 +1,25 @@ +'use strict'; + +module.exports = function (task) { + task + .id('callback-builder') + .description('Callback task') + + .option('setupCallback', null, function () {}) + .option('teardownCallback', null, function () {}) + .option('callback', null, function () {}) + .option('someOption', null, 'default') + + .setup(function (opt, ctx, next) { + opt.setupCallback.call(this, opt, ctx); + next(); + }) + .teardown(function (opt, ctx, next) { + opt.teardownCallback.call(this, opt, ctx); + next(); + }) + .do(function (opt, ctx, next) { + opt.callback.call(this, opt, ctx); + next(); + }); +}; \ No newline at end of file diff --git a/test/task_builder.js b/test/task_builder.js index 9dc5720..edf58b7 100644 --- a/test/task_builder.js +++ b/test/task_builder.js @@ -2,10 +2,11 @@ var TaskBuilder = require('../lib/TaskBuilder'), expect = require('expect.js'), - taskBuilder + taskBuilder, + callbackTask = require('./helpers/tasks/callback-builder') ; -module.exports = function () { +module.exports = function (automaton) { beforeEach(function () { taskBuilder = new TaskBuilder(); @@ -14,128 +15,214 @@ module.exports = function () { taskBuilder = null; }); - describe('TaskBuilder', function () { - it('should create a task with an id', function () { - var task = taskBuilder.id('task1').toObject(); + describe('Task builder', function () { + describe('Builder', function () { + it('should create a task with an id', function () { + var task = taskBuilder.id('task1').toObject(); - expect(task).to.be.eql({ tasks: [], id: 'task1' }); - }); - - it('should create a task with a name', function () { - var task = taskBuilder.name('task_name').toObject(); - - expect(task).to.be.eql({ tasks: [], name: 'task_name' }); - }); - - it('should create a task with a description', function () { - var task = taskBuilder.description('task description').toObject(); - - expect(task).to.be.eql({ tasks: [], description: 'task description' }); - }); - - it('should create a task with an author', function () { - var task = taskBuilder.author('author name').toObject(); - - expect(task).to.be.eql({ tasks: [], author: 'author name' }); - }); - - it('should create a task with an option', function () { - var task = taskBuilder.option('option1').toObject(); - - expect(task).to.be.eql({ tasks: [], options: { option1: {} } }); - }); - - it('should create a task with an option that have a description', function () { - var task = taskBuilder.option('option1', 'description of option1').toObject(); - - expect(task).to.be.eql({ tasks: [], options: { option1: { description: 'description of option1' } } }); - }); - - it('should create a task with an option that have a default', function () { - var task = taskBuilder.option('option1', null, false).toObject(); - - expect(task).to.be.eql({ tasks: [], options: { option1: { default: false } } }); - taskBuilder.option('option1', null, null).toObject(); - expect(task).to.be.eql({ tasks: [], options: { option1: { default: null } } }); - }); - - it('should create a task with an option that have a description and a default', function () { - var task = taskBuilder.option('option1', 'description of option1', false).toObject(); - - expect(task).to.be.eql({ tasks: [], options: { option1: { description: 'description of option1', default: false } } }); - }); - - it('should create a task with a replaced option', function () { - var task = taskBuilder.option('option1', 'description of option1', false) - .option('option1', 'another description for option1', true) - .toObject(); - - expect(task).to.be.eql({ tasks: [], options: { option1: { description: 'another description for option1', default: true } } }); - }); - - it('should create a task with setup', function () { - var setupFunc = function (opts, ctx, next) { next(); }, - task = taskBuilder.setup(setupFunc).toObject(); - - expect(task).to.be.eql({ tasks: [], setup: setupFunc }); - }); - - it('should create a task with teardown', function () { - var teardownFunc = function (opts, ctx, next) { next(); }, - task = taskBuilder.teardown(teardownFunc).toObject(); + expect(task).to.be.eql({ tasks: [], id: 'task1' }); + }); - expect(task).to.be.eql({ tasks: [], teardown: teardownFunc }); - }); + it('should create a task with a name', function () { + var task = taskBuilder.name('task_name').toObject(); - it('should create a task with a subtask', function () { - var task = taskBuilder.do('subtask1').toObject(); + expect(task).to.be.eql({ tasks: [], name: 'task_name' }); + }); - expect(task).to.be.eql({ tasks: [ { task: 'subtask1' } ] }); + it('should create a task with a description', function () { + var task = taskBuilder.description('task description').toObject(); + + expect(task).to.be.eql({ tasks: [], description: 'task description' }); + }); + + it('should create a task with an author', function () { + var task = taskBuilder.author('author name').toObject(); + + expect(task).to.be.eql({ tasks: [], author: 'author name' }); + }); + + it('should create a task with an option', function () { + var task = taskBuilder.option('option1').toObject(); + + expect(task).to.be.eql({ tasks: [], options: { option1: {} } }); + }); + + it('should create a task with an option that have a description', function () { + var task = taskBuilder.option('option1', 'description of option1').toObject(); + + expect(task).to.be.eql({ tasks: [], options: { option1: { description: 'description of option1' } } }); + }); + + it('should create a task with an option that have a default', function () { + var task = taskBuilder.option('option1', null, false).toObject(); + + expect(task).to.be.eql({ tasks: [], options: { option1: { default: false } } }); + taskBuilder.option('option1', null, null).toObject(); + expect(task).to.be.eql({ tasks: [], options: { option1: { default: null } } }); + }); + + it('should create a task with an option that have a description and a default', function () { + var task = taskBuilder.option('option1', 'description of option1', false).toObject(); + + expect(task).to.be.eql({ tasks: [], options: { option1: { description: 'description of option1', default: false } } }); + }); + + it('should create a task with a replaced option', function () { + var task = taskBuilder.option('option1', 'description of option1', false) + .option('option1', 'another description for option1', true) + .toObject(); + + expect(task).to.be.eql({ tasks: [], options: { option1: { description: 'another description for option1', default: true } } }); + }); + + it('should create a task with setup', function () { + var setupFunc = function (opts, ctx, next) { next(); }, + task = taskBuilder.setup(setupFunc).toObject(); + + expect(task).to.be.eql({ tasks: [], setup: setupFunc }); + }); + + it('should create a task with teardown', function () { + var teardownFunc = function (opts, ctx, next) { next(); }, + task = taskBuilder.teardown(teardownFunc).toObject(); + + expect(task).to.be.eql({ tasks: [], teardown: teardownFunc }); + }); + + it('should create a task with a subtask', function () { + var task = taskBuilder.do('subtask1').toObject(); + + expect(task).to.be.eql({ tasks: [ { task: 'subtask1' } ] }); + }); + + it('should create a task with a subtask that have a configuration', function () { + var task = taskBuilder.do('subtask1', { options: {}, mute: true, fatal: false }).toObject(); + + expect(task).to.be.eql({ tasks: [ { task: 'subtask1', options: {}, mute: true, fatal: false } ] }); + }); + + it('should create a complete task', function () { + var setupFunc = function (opts, ctx, next) { next(); }, + teardownFunc = function (opts, ctx, next) { next(); }, + task = taskBuilder.id('task1') + .name('task_name') + .description('task description') + .author('task author') + .option('option1', 'option description', false) + .setup(setupFunc) + .teardown(teardownFunc) + .do('subtask1', { options: {}, mute: true, fatal: false }) + .toObject(), + + expectedTask = { + id: 'task1', + name: 'task_name', + description: 'task description', + author: 'task author', + options: { + option1: { + description: 'option description', + default: false + } + }, + setup: setupFunc, + teardown: teardownFunc, + tasks: [ + { + task: 'subtask1', + options: {}, + mute: true, + fatal: false + } + ] + }; + + expect(task).to.be.eql(expectedTask); + }); }); - it('should create a task with a subtask that have a configuration', function () { - var task = taskBuilder.do('subtask1', { options: {}, mute: true, fatal: false }).toObject(); - - expect(task).to.be.eql({ tasks: [ { task: 'subtask1', options: {}, mute: true, fatal: false } ] }); - }); + describe('TaskBuilder integration', function () { + it('should run built tasks by id', function (done) { + var ok = false; - it('should create a complete task', function () { - var setupFunc = function (opts, ctx, next) { next(); }, - teardownFunc = function (opts, ctx, next) { next(); }, - task = taskBuilder.id('task1') - .name('task_name') - .description('task description') - .author('task author') - .option('option1', 'option description', false) - .setup(setupFunc) - .teardown(teardownFunc) - .do('subtask1', { options: {}, mute: true, fatal: false }) - .toObject(), - - expectedTask = { - id: 'task1', - name: 'task_name', - description: 'task description', - author: 'task author', - options: { - option1: { - description: 'option description', - default: false + automaton.run({ + tasks: [ + { + task: 'callback-builder', + options: { + callback: function () { + ok = true; + } + } } - }, - setup: setupFunc, - teardown: teardownFunc, + ] + }, null, function (err) { + if (err) { + throw err; + } + + expect(ok).to.equal(true); + done(); + }); + }); + + it('should run built tasks directly by id', function (done) { + var ok = false; + + automaton.run('callback-builder', { + callback: function () { + ok = true; + } + }, function (err) { + if (err) { + throw err; + } + + expect(ok).to.equal(true); + done(); + }); + }); + + it('should run built tasks reference', function (done) { + var ok = false; + + automaton.run({ tasks: [ { - task: 'subtask1', - options: {}, - mute: true, - fatal: false + task: callbackTask, + options: { + callback: function () { + ok = true; + } + } } ] - }; - - expect(task).to.be.eql(expectedTask); + }, null, function (err) { + if (err) { + throw err; + } + + expect(ok).to.equal(true); + done(); + }); + }); + + it('should run built tasks directly by reference', function (done) { + var ok = false; + + automaton.run(callbackTask, { + callback: function () { + ok = true; + } + }, function (err) { + if (err) { + throw err; + } + + expect(ok).to.equal(true); + done(); + }); + }); }); }); }; \ No newline at end of file