123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- /*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2012 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
- 'use strict';
- var grunt = require('../grunt');
- // Nodejs libs.
- var path = require('path');
- // Extend generic "task" util lib.
- var parent = grunt.util.task.create();
- // The module to be exported.
- var task = module.exports = Object.create(parent);
- // A temporary registry of tasks and metadata.
- var registry = {tasks: [], untasks: [], meta: {}};
- // The last specified tasks message.
- var lastInfo;
- // Number of levels of recursion when loading tasks in collections.
- var loadTaskDepth = 0;
- // Keep track of the number of log.error() calls.
- var errorcount;
- // Override built-in registerTask.
- task.registerTask = function(name) {
- // Add task to registry.
- registry.tasks.push(name);
- // Register task.
- parent.registerTask.apply(task, arguments);
- // This task, now that it's been registered.
- var thisTask = task._tasks[name];
- // Metadata about the current task.
- thisTask.meta = grunt.util._.clone(registry.meta);
- // Override task function.
- var _fn = thisTask.fn;
- thisTask.fn = function(arg) {
- // Initialize the errorcount for this task.
- errorcount = grunt.fail.errorcount;
- // Return the number of errors logged during this task.
- Object.defineProperty(this, 'errorCount', {
- enumerable: true,
- get: function() {
- return grunt.fail.errorcount - errorcount;
- }
- });
- // Expose task.requires on `this`.
- this.requires = task.requires.bind(task);
- // Expose config.requires on `this`.
- this.requiresConfig = grunt.config.requires;
- // Return an options object with the specified defaults overriden by task-
- // specific overrides, via the "options" property.
- this.options = function() {
- var args = [{}].concat(grunt.util.toArray(arguments)).concat([
- grunt.config([name, 'options'])
- ]);
- return grunt.util._.extend.apply(null, args);
- };
- // If this task was an alias or a multi task called without a target,
- // only log if in verbose mode.
- var logger = _fn.alias || (thisTask.multi && (!arg || arg === '*')) ? 'verbose' : 'log';
- // Actually log.
- grunt[logger].header('Running "' + this.nameArgs + '"' +
- (this.name !== this.nameArgs ? ' (' + this.name + ')' : '') + ' task');
- // If --debug was specified, log the path to this task's source file.
- grunt[logger].debug('Task source: ' + thisTask.meta.filepath);
- // Actually run the task.
- return _fn.apply(this, arguments);
- };
- return task;
- };
- // Multi task targets can't start with _ or be a reserved property (options).
- function isValidMultiTaskTarget(target) {
- return !/^_|^options$/.test(target);
- }
- // Normalize multi task files.
- task.normalizeMultiTaskFiles = function(data, target) {
- var prop, obj;
- var files = [];
- if (grunt.util.kindOf(data) === 'object') {
- if ('src' in data || 'dest' in data) {
- obj = {};
- for (prop in data) {
- if (prop !== 'options') {
- obj[prop] = data[prop];
- }
- }
- files.push(obj);
- } else if (grunt.util.kindOf(data.files) === 'object') {
- for (prop in data.files) {
- files.push({src: data.files[prop], dest: grunt.config.process(prop)});
- }
- } else if (Array.isArray(data.files)) {
- data.files.forEach(function(obj) {
- var prop;
- if ('src' in obj || 'dest' in obj) {
- files.push(obj);
- } else {
- for (prop in obj) {
- files.push({src: obj[prop], dest: grunt.config.process(prop)});
- }
- }
- });
- }
- } else {
- files.push({src: data, dest: grunt.config.process(target)});
- }
- // If no src/dest or files were specified, return an empty files array.
- if (files.length === 0) {
- grunt.verbose.writeln('File: ' + '[no files]'.red);
- return [];
- }
- // Process all normalized file objects.
- files = grunt.util._(files).chain().forEach(function(obj) {
- if (!('src' in obj) || !obj.src) { return; }
- // Normalize .src properties to flattened array.
- if (Array.isArray(obj.src)) {
- obj.src = grunt.util._.flatten(obj.src);
- } else {
- obj.src = [obj.src];
- }
- }).map(function(obj) {
- // Build options object, removing unwanted properties.
- var expandOptions = grunt.util._.extend({}, obj);
- delete expandOptions.src;
- delete expandOptions.dest;
- // Expand file mappings.
- if (obj.expand) {
- return grunt.file.expandMapping(obj.src, obj.dest, expandOptions).map(function(mapObj) {
- // Copy obj properties to result.
- var result = grunt.util._.extend({}, obj);
- // Make a clone of the orig obj available.
- result.orig = grunt.util._.extend({}, obj);
- // Set .src and .dest, processing both as templates.
- result.src = grunt.config.process(mapObj.src);
- result.dest = grunt.config.process(mapObj.dest);
- // Remove unwanted properties.
- ['expand', 'cwd', 'flatten', 'rename', 'ext'].forEach(function(prop) {
- delete result[prop];
- });
- return result;
- });
- }
- // Copy obj properties to result, adding an .orig property.
- var result = grunt.util._.extend({}, obj);
- // Make a clone of the orig obj available.
- result.orig = grunt.util._.extend({}, obj);
- if ('src' in result) {
- // Expose an expand-on-demand getter method as .src.
- Object.defineProperty(result, 'src', {
- enumerable: true,
- get: function fn() {
- var src;
- if (!('result' in fn)) {
- src = obj.src;
- // If src is an array, flatten it. Otherwise, make it into an array.
- src = Array.isArray(src) ? grunt.util._.flatten(src) : [src];
- // Expand src files, memoizing result.
- fn.result = grunt.file.expand(expandOptions, src);
- }
- return fn.result;
- }
- });
- }
- if ('dest' in result) {
- result.dest = obj.dest;
- }
- return result;
- }).flatten().value();
- // Log this.file src and dest properties when --verbose is specified.
- if (grunt.option('verbose')) {
- files.forEach(function(obj) {
- var output = [];
- if ('src' in obj) {
- output.push(obj.src.length > 0 ? grunt.log.wordlist(obj.src) : '[no src]'.red);
- }
- if ('dest' in obj) {
- output.push('-> ' + (obj.dest ? String(obj.dest).cyan : '[no dest]'.red));
- }
- if (output.length > 0) {
- grunt.verbose.writeln('Files: ' + output.join(' '));
- }
- });
- }
- return files;
- };
- // This is the most common "multi task" pattern.
- task.registerMultiTask = function(name, info, fn) {
- // If optional "info" string is omitted, shuffle arguments a bit.
- if (fn == null) {
- fn = info;
- info = 'Custom multi task.';
- }
- // Store a reference to the task object, in case the task gets renamed.
- var thisTask;
- task.registerTask(name, info, function(target) {
- // Guaranteed to always be the actual task name.
- var name = thisTask.name;
- // Arguments (sans target) as an array.
- this.args = grunt.util.toArray(arguments).slice(1);
- // If a target wasn't specified, run this task once for each target.
- if (!target || target === '*') {
- return task.runAllTargets(name, this.args);
- } else if (!isValidMultiTaskTarget(target)) {
- throw new Error('Invalid target "' + target + '" specified.');
- }
- // Fail if any required config properties have been omitted.
- this.requiresConfig([name, target]);
- // Return an options object with the specified defaults overriden by task-
- // and/or target-specific overrides, via the "options" property.
- this.options = function() {
- var targetObj = grunt.config([name, target]);
- var args = [{}].concat(grunt.util.toArray(arguments)).concat([
- grunt.config([name, 'options']),
- grunt.util.kindOf(targetObj) === 'object' ? targetObj.options : {}
- ]);
- return grunt.util._.extend.apply(null, args);
- };
- // Expose data on `this` (as well as task.current).
- this.data = grunt.config([name, target]);
- // Expose normalized files object.
- this.files = task.normalizeMultiTaskFiles(this.data, target);
- // Expose normalized, flattened, uniqued array of src files.
- Object.defineProperty(this, 'filesSrc', {
- enumerable: true,
- get: function() {
- return grunt.util._(this.files).chain().pluck('src').flatten().uniq().value();
- }.bind(this)
- });
- // Expose the current target.
- this.target = target;
- // Recreate flags object so that the target isn't set as a flag.
- this.flags = {};
- this.args.forEach(function(arg) { this.flags[arg] = true; }, this);
- // Call original task function, passing in the target and any other args.
- return fn.apply(this, this.args);
- });
- thisTask = task._tasks[name];
- thisTask.multi = true;
- };
- // Init tasks don't require properties in config, and as such will preempt
- // config loading errors.
- task.registerInitTask = function(name, info, fn) {
- task.registerTask(name, info, fn);
- task._tasks[name].init = true;
- };
- // Override built-in renameTask to use the registry.
- task.renameTask = function(oldname, newname) {
- // Add and remove task.
- registry.untasks.push(oldname);
- registry.tasks.push(newname);
- // Actually rename task.
- return parent.renameTask.apply(task, arguments);
- };
- // If a property wasn't passed, run all task targets in turn.
- task.runAllTargets = function(taskname, args) {
- // Get an array of sub-property keys under the given config object.
- var targets = Object.keys(grunt.config.getRaw(taskname) || {});
- // Fail if there are no actual properties to iterate over.
- if (targets.length === 0) {
- grunt.log.error('No "' + taskname + '" targets found.');
- return false;
- }
- // Iterate over all valid target properties, running a task for each.
- targets.filter(isValidMultiTaskTarget).forEach(function(target) {
- // Be sure to pass in any additionally specified args.
- task.run([taskname, target].concat(args || []).join(':'));
- });
- };
- // Load tasks and handlers from a given tasks file.
- var loadTaskStack = [];
- function loadTask(filepath) {
- // In case this was called recursively, save registry for later.
- loadTaskStack.push(registry);
- // Reset registry.
- registry = {tasks: [], untasks: [], meta: {info: lastInfo, filepath: filepath}};
- var filename = path.basename(filepath);
- var msg = 'Loading "' + filename + '" tasks...';
- var regCount = 0;
- var fn;
- try {
- // Load taskfile.
- fn = require(path.resolve(filepath));
- if (typeof fn === 'function') {
- fn.call(grunt, grunt);
- }
- grunt.verbose.write(msg).ok();
- // Log registered/renamed/unregistered tasks.
- ['un', ''].forEach(function(prefix) {
- var list = grunt.util._.chain(registry[prefix + 'tasks']).uniq().sort().value();
- if (list.length > 0) {
- regCount++;
- grunt.verbose.writeln((prefix ? '- ' : '+ ') + grunt.log.wordlist(list));
- }
- });
- if (regCount === 0) {
- grunt.verbose.error('No tasks were registered or unregistered.');
- }
- } catch(e) {
- // Something went wrong.
- grunt.log.write(msg).error().verbose.error(e.stack).or.error(e);
- }
- // Restore registry.
- registry = loadTaskStack.pop() || {};
- }
- // Log a message when loading tasks.
- function loadTasksMessage(info) {
- // Only keep track of names of top-level loaded tasks and collections,
- // not sub-tasks.
- if (loadTaskDepth === 0) { lastInfo = info; }
- grunt.verbose.subhead('Registering ' + info + ' tasks.');
- }
- // Load tasks and handlers from a given directory.
- function loadTasks(tasksdir) {
- try {
- var files = grunt.file.glob.sync('*.{js,coffee}', {cwd: tasksdir, maxDepth: 1});
- // Load tasks from files.
- files.forEach(function(filename) {
- loadTask(path.join(tasksdir, filename));
- });
- } catch(e) {
- grunt.log.verbose.error(e.stack).or.error(e);
- }
- }
- // Load tasks and handlers from a given directory.
- task.loadTasks = function(tasksdir) {
- loadTasksMessage('"' + tasksdir + '"');
- if (grunt.file.exists(tasksdir)) {
- loadTasks(tasksdir);
- } else {
- grunt.log.error('Tasks directory "' + tasksdir + '" not found.');
- }
- };
- // Load tasks and handlers from a given locally-installed Npm module (installed
- // relative to the base dir).
- task.loadNpmTasks = function(name) {
- loadTasksMessage('"' + name + '" local Npm module');
- var root = path.resolve('node_modules');
- var pkgfile = path.join(root, name, 'package.json');
- var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []};
- // Process collection plugins.
- if (pkg.keywords && pkg.keywords.indexOf('gruntcollection') !== -1) {
- loadTaskDepth++;
- Object.keys(pkg.dependencies).forEach(function(depName) {
- // Npm sometimes pulls dependencies out if they're shared, so find
- // upwards if not found locally.
- var filepath = grunt.file.findup('node_modules/' + depName, {
- cwd: path.resolve('node_modules', name),
- nocase: true
- });
- if (filepath) {
- // Load this task plugin recursively.
- task.loadNpmTasks(path.relative(root, filepath));
- }
- });
- loadTaskDepth--;
- return;
- }
- // Process task plugins.
- var tasksdir = path.join(root, name, 'tasks');
- if (grunt.file.exists(tasksdir)) {
- loadTasks(tasksdir);
- } else {
- grunt.log.error('Local Npm module "' + name + '" not found. Is it installed?');
- }
- };
- // Initialize tasks.
- task.init = function(tasks, options) {
- if (!options) { options = {}; }
- // Were only init tasks specified?
- var allInit = tasks.length > 0 && tasks.every(function(name) {
- var obj = task._taskPlusArgs(name).task;
- return obj && obj.init;
- });
- // Get any local Gruntfile or tasks that might exist. Use --gruntfile override
- // if specified, otherwise search the current directory or any parent.
- var gruntfile = allInit ? null : grunt.option('gruntfile') ||
- grunt.file.findup('Gruntfile.{js,coffee}', {nocase: true});
- var msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
- if (gruntfile && grunt.file.exists(gruntfile)) {
- grunt.verbose.writeln().write(msg).ok();
- // Change working directory so that all paths are relative to the
- // Gruntfile's location (or the --base option, if specified).
- process.chdir(grunt.option('base') || path.dirname(gruntfile));
- // Load local tasks, if the file exists.
- loadTasksMessage('Gruntfile');
- loadTask(gruntfile);
- } else if (options.help || allInit) {
- // Don't complain about missing Gruntfile.
- } else if (grunt.option('gruntfile')) {
- // If --config override was specified and it doesn't exist, complain.
- grunt.log.writeln().write(msg).error();
- grunt.fatal('Unable to find "' + gruntfile + '" Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
- } else if (!grunt.option('help')) {
- grunt.verbose.writeln().write(msg).error();
- grunt.log.writelns(
- 'A valid Gruntfile could not be found. Please see the getting ' +
- 'started guide for more information on how to configure grunt: ' +
- 'http://gruntjs.com/getting-started'
- );
- grunt.fatal('Unable to find Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
- }
- // Load all user-specified --npm tasks.
- (grunt.option('npm') || []).forEach(task.loadNpmTasks);
- // Load all user-specified --tasks.
- (grunt.option('tasks') || []).forEach(task.loadTasks);
- };
|