| var util = require('util') // Native Node util module |
| , fs = require('fs') |
| , path = require('path') |
| , existsSync = typeof fs.existsSync == 'function' ? |
| fs.existsSync : path.existsSync |
| , EventEmitter = require('events').EventEmitter |
| , Task |
| , TaskBase |
| , utils = require('../utils'); |
| |
| /** |
| @name jake |
| @namespace jake |
| */ |
| /** |
| @name jake.Task |
| @constructor |
| @augments EventEmitter |
| @description A Jake Task |
| |
| @param {String} name The name of the Task |
| @param {Array} [prereqs] Prerequisites to be run before this task |
| @param {Function} [action] The action to perform for this task |
| @param {Object} [opts] |
| @param {Array} [opts.asyc=false] Perform this task asynchronously. |
| If you flag a task with this option, you must call the global |
| `complete` method inside the task's action, for execution to proceed |
| to the next task. |
| */ |
| Task = function () { |
| // Do constructor-work only on actual instances, not when used |
| // for inheritance |
| if (arguments.length) { |
| this.init.apply(this, arguments); |
| } |
| }; |
| |
| util.inherits(Task, EventEmitter); |
| |
| TaskBase = new (function () { |
| |
| // Parse any positional args attached to the task-name |
| var parsePrereqName = function (name) { |
| var taskArr = name.split('[') |
| , taskName = taskArr[0] |
| , taskArgs = []; |
| if (taskArr[1]) { |
| taskArgs = taskArr[1].replace(/\]$/, ''); |
| taskArgs = taskArgs.split(','); |
| } |
| return { |
| name: taskName |
| , args: taskArgs |
| }; |
| }; |
| |
| /** |
| @name jake.Task#event:complete |
| @event |
| */ |
| |
| this.init = function (name, prereqs, action, options) { |
| var opts = options || {}; |
| |
| this._currentPrereqIndex = 0; |
| |
| this.name = name; |
| this.prereqs = prereqs; |
| this.action = action; |
| this.async = false; |
| this.done = false; |
| this.fullName = null; |
| this.description = null; |
| this.args = []; |
| this.namespace = null; |
| |
| // Support legacy async-flag -- if not explicitly passed or falsy, will |
| // be set to empty-object |
| if (typeof opts == 'boolean' && opts === true) { |
| this.async = true; |
| } |
| else { |
| if (opts.async) { |
| this.async = true; |
| } |
| } |
| }; |
| |
| /** |
| @name jake.Task#invoke |
| @function |
| @description Runs prerequisites, then this task. If the task has already |
| been run, will not run the task again. |
| */ |
| this.invoke = function () { |
| jake._invocationChain.push(this); |
| this.args = Array.prototype.slice.call(arguments); |
| this.runPrereqs(); |
| }; |
| |
| /** |
| @name jake.Task#reenable |
| @function |
| @description Runs this task, without running any prerequisites. If the task |
| has already been run, it will still run it again. |
| */ |
| this.execute = function () { |
| jake._invocationChain.push(this); |
| this.reenable(); |
| this.run(); |
| }; |
| |
| this.runPrereqs = function () { |
| if (this.prereqs && this.prereqs.length) { |
| this.nextPrereq(); |
| } |
| else { |
| this.run(); |
| } |
| }; |
| |
| this.nextPrereq = function () { |
| var self = this |
| , index = this._currentPrereqIndex |
| , name = this.prereqs[index] |
| , absolute |
| , prereq |
| , parsed |
| , filePath |
| , stats; |
| |
| if (name) { |
| parsed = parsePrereqName(name); |
| absolute = parsed.name[0] === '^'; |
| |
| if (absolute) { |
| parsed.name = parsed.name.slice(1); |
| prereq = jake.Task[parsed.name]; |
| } else { |
| prereq = this.namespace.resolve(parsed.name); |
| } |
| |
| // Task doesn't exist, may be a static file |
| if (!prereq) { |
| // May be namespaced |
| filePath = name.split(':').pop(); |
| // Create a dummy FileTask if file actually exists |
| if (existsSync(filePath)) { |
| // If there's not already an existing dummy FileTask for it, |
| // create one |
| prereq = jake.Task[filePath]; |
| if (!prereq) { |
| stats = fs.statSync(filePath); |
| prereq = new jake.FileTask(filePath); |
| prereq.modTime = stats.mtime; |
| prereq.dummy = true; |
| // Put this dummy Task in the global Tasks list so |
| // modTime will be eval'd correctly |
| jake.Task[filePath] = prereq; |
| } |
| } |
| // Otherwise it's not a valid task |
| else { |
| throw new Error('Unknown task "' + name + '"'); |
| } |
| } |
| |
| // Do when done |
| if (prereq.done) { |
| self.handlePrereqComplete(prereq); |
| } else { |
| prereq.once('complete', function () { |
| self.handlePrereqComplete(prereq); |
| }); |
| prereq.invoke.apply(prereq, parsed.args); |
| } |
| } |
| }; |
| |
| this.reenable = function (deep) { |
| var prereqs |
| , prereq; |
| this.done = false; |
| if (deep && this.prereqs) { |
| prereqs = this.prereqs; |
| for (var i = 0, ii = prereqs.length; i < ii; i++) { |
| prereq = jake.Task[prereqs[i]]; |
| if (prereq) { |
| prereq.reenable(deep); |
| } |
| } |
| } |
| }; |
| |
| this.handlePrereqComplete = function (prereq) { |
| var self = this; |
| this._currentPrereqIndex++; |
| if (this._currentPrereqIndex < this.prereqs.length) { |
| setTimeout(function () { |
| self.nextPrereq(); |
| }, 0); |
| } |
| else { |
| this.run(); |
| } |
| }; |
| |
| this.shouldRunAction = function () { |
| if (this.done || typeof this.action != 'function') { |
| return false |
| } |
| return true; |
| }; |
| |
| this.run = function () { |
| var runAction = this.shouldRunAction(); |
| if (runAction) { |
| this.emit('start'); |
| try { |
| this.action.apply(this, this.args); |
| } |
| catch (e) { |
| this.emit('error', e); |
| return; // Bail out, not complete |
| } |
| } |
| if (!(runAction && this.async)) { |
| complete(); |
| } |
| }; |
| |
| this.complete = function () { |
| this._currentPrereqIndex = 0; |
| this.done = true; |
| this.emit('complete'); |
| }; |
| |
| })(); |
| utils.mixin(Task.prototype, TaskBase); |
| |
| exports.Task = Task; |