'use strict';
/**
* @module task-loader
*/
var assign = require('lodash/object/assign'),
fs = require('fs'),
gutil = require('gulp-util'),
lang = require('lodash/lang'),
path = require('path'),
util = require('util');
var _task;
var runSequence;
/**
* @namespace
* @property {object} exports - task definition
* @property {function} exports.load - task definition
* @property {object} exports.context - task definition
*/
module.exports = {
load: load,
/**
* @namespace
* @property {object} context - tasks context
* @property {function} context.addHelpTask - adds help task
* @property {array<string>} context.taskNames - list of task names
*/
context: {}
};
/**
* Loads modules from task dir and creates modules with given gulp object
* @param {string} taskDir directory where tasks modules remain
* @param {object} gulp the current gulp object
* @return {object} context tasks context
*/
function load(taskDir, gulp) {
runSequence = require('run-sequence')
.use(gulp);
if (arguments.length < 2) {
throw 'Expecting at least two arguments: taskDir and gulp';
}
// let's first catch all the arguments here
var args = Array.prototype.slice.apply(arguments);
// let's add a state object we need to haul with the parser
var state = {
defaultTaskNames: [],
sequenceCount: 0
};
args.unshift(state);
var taskNames = [];
_task = function () {
var taskArgs = Array.prototype.slice.apply(arguments);
taskNames.push(taskArgs[0]);
return gulp.task.apply(gulp, taskArgs);
};
_parse.apply(this, args);
if (state.defaultTaskNames.length) {
_task('default', state.defaultTaskNames);
}
var taskInfo = require('../task-info')(gulp);
assign(module.exports.context, {
taskNames: taskNames,
defaultTaskNames: state.defaultTaskNames,
addHelpTask: addHelpTask.bind(this, gulp, taskInfo),
taskTree: taskInfo.taskTree,
cliHelp: taskInfo.cliHelp.bind(this, gutil.env)
});
return module.exports.context;
}
function addHelpTask(gulp, taskInfo, options) {
options = options || {};
gulp.task('help', function () {
console.log('help', taskInfo.cliHelp(gutil.env, options));
});
gulp.tasks.help.description = 'Show help';
gulp.tasks.help.options = {
'-a, --all': 'Also show tasks without descriptions'
};
gulp.tasks.help.priority = !lang.isUndefined(options.priority) ?
options.priority : 0;
if (lang.isUndefined(options.isDefault) || options.isDefault === true) {
var defaultTask = gulp.tasks.default;
if (defaultTask) {
defaultTask.dep.push('help');
} else {
gulp.task('default', ['help']);
}
}
}
function _parse(state, taskDir, gulp) {
taskDir = path.resolve(taskDir);
var customOptions = ['usage',
'description',
'seq',
'options',
'isDefault'
];
var args = Array.prototype.slice.call(arguments)
.splice(2);
var currentPriority = -10;
// get all tasks
// todo: clean up this mess
fs.readdirSync(taskDir)
.map(function (file) {
var fullPath = taskDir + path.sep + file;
var stats = fs.statSync(fullPath);
if (stats.isFile() && path.extname(file) === '.js') {
var tasks;
var moduleId = path.join(taskDir, file);
var requiredTasks = require(moduleId);
module.children.forEach(function (childModule) {
if (moduleId === childModule.id) {
childModule.tasksContext = module.exports.context;
}
});
if (lang.isFunction(requiredTasks)) {
tasks =
requiredTasks.apply(null, args);
if (lang.isFunction(tasks)) {
var taskFn = tasks;
var taskName = path.basename(file, '.js');
tasks = {};
tasks[taskName] = {
fn: taskFn
};
}
} else {
tasks = requiredTasks;
}
// let's assume it's an object
if (lang.isObject(tasks)) {
Object.keys(tasks)
.map(function (taskName2) {
var task = tasks[taskName2];
var taskArgs = [taskName2];
if (lang.isFunction(task)) {
taskArgs.push([]);
taskArgs.push(task);
} else if (lang.isObject(task)) {
// Check for default tasks
_defaultTasks(gulp, taskName2, task, state);
// sequences
_sequences(gulp, taskName2, task, state);
// dependencies
var deps = [];
var depIndex = 1;
var anonCount = 0;
if (lang.isArray(task.dep)) {
task.dep.map(function (taskDep) {
var dep = taskDep;
if (lang.isFunction(taskDep)) {
var fnName = _getFunctionName(taskDep);
if (!fnName) {
anonCount++;
fnName = util.format('anonymous-%s',
anonCount);
}
var depName = util.format(
'%s-(index: %s, fnName: %s)', taskName2,
depIndex, fnName);
_task.apply(gulp, [
depName, [],
taskDep
]);
depIndex++;
dep = depName;
}
deps.push(dep);
});
}
taskArgs.push(deps);
if (task.fn) {
taskArgs.push(task.fn);
}
} else if (lang.isArray(task)) {
taskArgs.push(task);
}
if (taskArgs.length > 1) {
_task.apply(gulp,
taskArgs);
// priority
if (task.description) {
var priority = task.priority;
if (!priority) {
priority = currentPriority;
currentPriority = currentPriority - 10;
}
gulp.tasks[taskName2].priority = priority;
}
gulp.tasks[taskName2].file = path.basename(file);
gulp.tasks[taskName2].path = path.resolve(taskDir);
customOptions.map(function (
customOption) {
var taskOption = task[
customOption];
if (taskOption) {
gulp.tasks[taskName2]
[
customOption
] = taskOption;
}
});
}
});
}
}
});
}
function _defaultTasks(gulp, taskName, task, state) {
// default task(s)
if (task.isDefault) {
state.defaultTaskNames.push(taskName);
}
}
function _sequences(gulp, taskName, task, state) {
if (task.seq) {
state.sequenceCount++;
var localState = {
seqIndex: 1,
anonCount: 0
};
task.seq = _parseSequence(gulp, taskName, task.seq, state, localState);
// If task does not have a function assigned then create one...
if (!task.fn) {
task.fn = function (done) {
runSequence.apply(
null, task.seq
.concat(
done));
};
} else {
// ... otherwise chop it off, and place it in a new task at the very end
// of the sequence. Yes I could monkey patch the task function but that's
// a bit too ghetto I think.
var subName = taskName + '-post';
var origFn = task.fn;
_task.apply(gulp, [
subName, [],
origFn
]);
task.fn = function (done) {
runSequence.apply(
null, task.seq
.concat(
subName
)
.concat(
done));
};
}
}
}
function _parseSequence(gulp, taskName, seqTasks, state, localState) {
return seqTasks.map(function (seqTask) {
if (lang.isString(seqTask)) {
return seqTask;
} else if (lang.isFunction(seqTask)) {
var fnName = _getFunctionName(seqTask);
if (!fnName) {
localState.anonCount++;
fnName = util.format('anonymous-%s',
localState.anonCount);
}
var seqName = util.format(
'%s-(index: %s, fnName: %s)',
taskName,
localState.seqIndex, fnName);
_task.apply(gulp, [
seqName, [],
seqTask
]);
localState.seqIndex++;
return seqName;
} else if (lang.isArray(seqTask)) {
state.sequenceCount++;
return _parseSequence(gulp, taskName, seqTask, state, localState);
}
});
}
function _getFunctionName(fn) {
var fnName = fn.toString();
fnName = fnName.substr('function '.length);
fnName = fnName.substr(0, fnName.indexOf('('));
return fnName;
}