grunt
Version:
A task-based command line build tool for JavaScript projects.
179 lines (163 loc) • 6.09 kB
JavaScript
/*
* grunt
* https://github.com/cowboy/grunt
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
var path = require('path');
var fs = require('fs');
// An internal registry of tasks and handlers.
var registry = {};
// Extend generic "task" util lib.
var parent = util.task.create();
exports = module.exports = Object.create(parent);
// If any log.error() calls occurred, then the task had errors.
var errorcount;
exports.hadErrors = function() {
return fail.errorcount > errorcount;
};
// Override built-in registerTask.
exports.registerTask = function(name, info, fn) {
// Add task to registry.
registry.tasks.push(name);
// Register task.
parent.registerTask.apply(this, arguments);
// This task, now that it's been registered.
var task = this._tasks[name];
// Override task function.
var _fn = task.fn;
task.fn = function() {
// Make a list of task-related extras paths available.
this.extraspaths = file.taskpaths.bind(file, this.name);
// Initialize the errorcount for this task.
errorcount = fail.errorcount;
// If this task was an alias or a basic task called without arguments,
// only log if in verbose mode.
var logger = _fn.alias || (task.basic && arguments.length === 0) ? verbose : log;
// Actually log.
logger.header('Running "' + this.nameArgs + '"' +
(this.name !== this.nameArgs ? ' (' + this.name + ')' : '') + ' task');
// Actually run the task.
return _fn.apply(this, arguments);
};
return this;
};
// This is the most common "basic task" pattern.
exports.registerBasicTask = function(name, info, fn) {
exports.registerTask(name, info, function(prop) {
// If an argument wasn't specified, run this task once for each config sub-prop.
if (!prop) {
return exports.runAllProps(name);
}
// Fail if any required config properties have been omitted.
config.requires([name, prop]);
// Call original task function, passing in only the stuff you really care about.
return fn.call(this, config([name, prop]), prop);
});
this._tasks[name].basic = true;
};
// Init tasks don't require properties in config, and as such will preempt
// config loading errors.
exports.registerInitTask = function(name, info, fn) {
exports.registerTask(name, info, fn);
this._tasks[name].init = true;
};
// Override built-in registerHelper to use the registry.
exports.registerHelper = function(name, fn) {
// Add task to registry.
registry.helpers.push(name);
// Actually register task.
return parent.registerHelper.apply(this, arguments);
};
// If a property wasn't passed, run all sub-property sub-tasks in turn. If the
// `configname` argument is omitted, use `taskname` for both.
exports.runAllProps = function(taskname, configname) {
if (!configname) { configname = taskname; }
// Get an array of sub-property keys under the given config object.
var props = Object.keys(config(configname) || {});
// Fail if there are no actual properties to iterate over.
if (props.length === 0) {
log.error('No "' + configname + '" configuration properties found.');
return false;
}
// Iterate over all properties not starting with _, running a task for each.
props.filter(function(prop) {
return !/^_/.test(prop);
}).forEach(function(prop) {
exports.run(taskname + ':' + prop);
});
};
// Load tasks and handlers from a given tasks file.
function loadTask(filepath, info) {
registry.tasks = [];
registry.helpers = [];
var file = path.basename(filepath);
var msg = 'Loading ' + info + ' "' + file + '" tasks and helpers...';
verbose.write(msg);
try {
// Load taskfile.
require(path.resolve(filepath));
verbose.ok();
if (registry.tasks.length === 0 && registry.helpers.length === 0) {
verbose.error('No tasks or helpers defined.');
} else {
if (registry.tasks.length > 0) {
verbose.writeln('Tasks: ' + log.wordlist(registry.tasks));
}
if (registry.helpers.length > 0) {
verbose.writeln('Helpers: ' + log.wordlist(registry.helpers));
}
}
} catch(e) {
// Something went wrong.
verbose.or.write(msg);
log.error().error(e.message);
}
}
// Initialize tasks.
exports.init = function(tasks, nocomplain) {
// Load all built-in and user-specified tasks.
file.taskpaths().reverse().forEach(function(tasksdir, i) {
try {
fs.readdirSync(tasksdir).filter(function(filename) {
// Filter out non-.js files.
return path.extname(filename).toLowerCase() === '.js';
}).forEach(function(filename) {
// Load task.
loadTask(path.join(tasksdir, filename), i > 0 ? 'additional' : 'built-in');
});
} catch(e) {
log.error(e.message);
}
});
// Don't complain if all specified tasks are "init" tasks.
nocomplain = nocomplain || tasks.every(function(name) {
var obj = exports._taskPlusArgs(name).task;
return obj && obj.init;
});
// Get any local config data or tasks that might exist.
// Use --config override if specified.
var configfile = option('config');
// If --config wasn't specified, search the current directory or any parent
// directory for a grunt.js file.
if (!configfile) {
configfile = file.findup(process.cwd(), 'grunt.js');
}
var basename = path.basename(configfile);
if (path.existsSync(configfile)) {
// Load local tasks, if the file exists.
loadTask(configfile, 'config file');
// Change working directory so that all paths are relative to the
// configfile's location (or the --base option, if specified).
process.chdir(option('base') || path.dirname(configfile));
} else if (nocomplain) {
// Don't complain about missing config file.
} else if (option('config')) {
// If --config override was specified and it doesn't exist, complain.
fail.fatal('Unable to find "' + configfile + '" config file.', 2);
} else if (!option('help')) {
fail.fatal('Unable to find "grunt.js" config file. Do you need any --help?', 2);
}
};