f31ff2e44661b944fffaa3b9c53a063299101769
[yaffs-website] / node_modules / grunt / lib / grunt / task.js
1 'use strict';
2
3 var grunt = require('../grunt');
4
5 // Nodejs libs.
6 var path = require('path');
7
8 // Extend generic "task" util lib.
9 var parent = grunt.util.task.create();
10
11 // The module to be exported.
12 var task = module.exports = Object.create(parent);
13
14 // A temporary registry of tasks and metadata.
15 var registry = {tasks: [], untasks: [], meta: {}};
16
17 // The last specified tasks message.
18 var lastInfo;
19
20 // Number of levels of recursion when loading tasks in collections.
21 var loadTaskDepth = 0;
22
23 // Keep track of the number of log.error() calls.
24 var errorcount;
25
26 // Override built-in registerTask.
27 task.registerTask = function(name) {
28   // Add task to registry.
29   registry.tasks.push(name);
30   // Register task.
31   parent.registerTask.apply(task, arguments);
32   // This task, now that it's been registered.
33   var thisTask = task._tasks[name];
34   // Metadata about the current task.
35   thisTask.meta = grunt.util._.clone(registry.meta);
36   // Override task function.
37   var _fn = thisTask.fn;
38   thisTask.fn = function(arg) {
39     // Guaranteed to always be the actual task name.
40     var name = thisTask.name;
41     // Initialize the errorcount for this task.
42     errorcount = grunt.fail.errorcount;
43     // Return the number of errors logged during this task.
44     Object.defineProperty(this, 'errorCount', {
45       enumerable: true,
46       get: function() {
47         return grunt.fail.errorcount - errorcount;
48       }
49     });
50     // Expose task.requires on `this`.
51     this.requires = task.requires.bind(task);
52     // Expose config.requires on `this`.
53     this.requiresConfig = grunt.config.requires;
54     // Return an options object with the specified defaults overwritten by task-
55     // specific overrides, via the "options" property.
56     this.options = function() {
57       var args = [{}].concat(grunt.util.toArray(arguments)).concat([
58         grunt.config([name, 'options'])
59       ]);
60       var options = grunt.util._.extend.apply(null, args);
61       grunt.verbose.writeflags(options, 'Options');
62       return options;
63     };
64     // If this task was an alias or a multi task called without a target,
65     // only log if in verbose mode.
66     var logger = _fn.alias || (thisTask.multi && (!arg || arg === '*')) ? 'verbose' : 'log';
67     // Actually log.
68     grunt[logger].header('Running "' + this.nameArgs + '"' +
69       (this.name !== this.nameArgs ? ' (' + this.name + ')' : '') + ' task');
70     // If --debug was specified, log the path to this task's source file.
71     grunt[logger].debug('Task source: ' + thisTask.meta.filepath);
72     // Actually run the task.
73     return _fn.apply(this, arguments);
74   };
75   return task;
76 };
77
78 // Multi task targets can't start with _ or be a reserved property (options).
79 function isValidMultiTaskTarget(target) {
80   return !/^_|^options$/.test(target);
81 }
82
83 // Normalize multi task files.
84 task.normalizeMultiTaskFiles = function(data, target) {
85   var prop, obj;
86   var files = [];
87   if (grunt.util.kindOf(data) === 'object') {
88     if ('src' in data || 'dest' in data) {
89       obj = {};
90       for (prop in data) {
91         if (prop !== 'options') {
92           obj[prop] = data[prop];
93         }
94       }
95       files.push(obj);
96     } else if (grunt.util.kindOf(data.files) === 'object') {
97       for (prop in data.files) {
98         files.push({src: data.files[prop], dest: grunt.config.process(prop)});
99       }
100     } else if (Array.isArray(data.files)) {
101       grunt.util._.flattenDeep(data.files).forEach(function(obj) {
102         var prop;
103         if ('src' in obj || 'dest' in obj) {
104           files.push(obj);
105         } else {
106           for (prop in obj) {
107             files.push({src: obj[prop], dest: grunt.config.process(prop)});
108           }
109         }
110       });
111     }
112   } else {
113     files.push({src: data, dest: grunt.config.process(target)});
114   }
115
116   // If no src/dest or files were specified, return an empty files array.
117   if (files.length === 0) {
118     grunt.verbose.writeln('File: ' + '[no files]'.yellow);
119     return [];
120   }
121
122   // Process all normalized file objects.
123   files = grunt.util._(files).chain().forEach(function(obj) {
124     if (!('src' in obj) || !obj.src) { return; }
125     // Normalize .src properties to flattened array.
126     if (Array.isArray(obj.src)) {
127       obj.src = grunt.util._.flatten(obj.src);
128     } else {
129       obj.src = [obj.src];
130     }
131   }).map(function(obj) {
132     // Build options object, removing unwanted properties.
133     var expandOptions = grunt.util._.extend({}, obj);
134     delete expandOptions.src;
135     delete expandOptions.dest;
136
137     // Expand file mappings.
138     if (obj.expand) {
139       return grunt.file.expandMapping(obj.src, obj.dest, expandOptions).map(function(mapObj) {
140         // Copy obj properties to result.
141         var result = grunt.util._.extend({}, obj);
142         // Make a clone of the orig obj available.
143         result.orig = grunt.util._.extend({}, obj);
144         // Set .src and .dest, processing both as templates.
145         result.src = grunt.config.process(mapObj.src);
146         result.dest = grunt.config.process(mapObj.dest);
147         // Remove unwanted properties.
148         ['expand', 'cwd', 'flatten', 'rename', 'ext'].forEach(function(prop) {
149           delete result[prop];
150         });
151         return result;
152       });
153     }
154
155     // Copy obj properties to result, adding an .orig property.
156     var result = grunt.util._.extend({}, obj);
157     // Make a clone of the orig obj available.
158     result.orig = grunt.util._.extend({}, obj);
159
160     if ('src' in result) {
161       // Expose an expand-on-demand getter method as .src.
162       Object.defineProperty(result, 'src', {
163         enumerable: true,
164         get: function fn() {
165           var src;
166           if (!('result' in fn)) {
167             src = obj.src;
168             // If src is an array, flatten it. Otherwise, make it into an array.
169             src = Array.isArray(src) ? grunt.util._.flatten(src) : [src];
170             // Expand src files, memoizing result.
171             fn.result = grunt.file.expand(expandOptions, src);
172           }
173           return fn.result;
174         }
175       });
176     }
177
178     if ('dest' in result) {
179       result.dest = obj.dest;
180     }
181
182     return result;
183   }).flatten().value();
184
185   // Log this.file src and dest properties when --verbose is specified.
186   if (grunt.option('verbose')) {
187     files.forEach(function(obj) {
188       var output = [];
189       if ('src' in obj) {
190         output.push(obj.src.length > 0 ? grunt.log.wordlist(obj.src) : '[no src]'.yellow);
191       }
192       if ('dest' in obj) {
193         output.push('-> ' + (obj.dest ? String(obj.dest).cyan : '[no dest]'.yellow));
194       }
195       if (output.length > 0) {
196         grunt.verbose.writeln('Files: ' + output.join(' '));
197       }
198     });
199   }
200
201   return files;
202 };
203
204 // This is the most common "multi task" pattern.
205 task.registerMultiTask = function(name, info, fn) {
206   // If optional "info" string is omitted, shuffle arguments a bit.
207   if (fn == null) {
208     fn = info;
209     info = 'Custom multi task.';
210   }
211   // Store a reference to the task object, in case the task gets renamed.
212   var thisTask;
213   task.registerTask(name, info, function(target) {
214     // Guaranteed to always be the actual task name.
215     var name = thisTask.name;
216     // Arguments (sans target) as an array.
217     this.args = grunt.util.toArray(arguments).slice(1);
218     // If a target wasn't specified, run this task once for each target.
219     if (!target || target === '*') {
220       return task.runAllTargets(name, this.args);
221     } else if (!isValidMultiTaskTarget(target)) {
222       throw new Error('Invalid target "' + target + '" specified.');
223     }
224     // Fail if any required config properties have been omitted.
225     this.requiresConfig([name, target]);
226     // Return an options object with the specified defaults overwritten by task-
227     // and/or target-specific overrides, via the "options" property.
228     this.options = function() {
229       var targetObj = grunt.config([name, target]);
230       var args = [{}].concat(grunt.util.toArray(arguments)).concat([
231         grunt.config([name, 'options']),
232         grunt.util.kindOf(targetObj) === 'object' ? targetObj.options : {}
233       ]);
234       var options = grunt.util._.extend.apply(null, args);
235       grunt.verbose.writeflags(options, 'Options');
236       return options;
237     };
238     // Expose the current target.
239     this.target = target;
240     // Recreate flags object so that the target isn't set as a flag.
241     this.flags = {};
242     this.args.forEach(function(arg) { this.flags[arg] = true; }, this);
243     // Expose data on `this` (as well as task.current).
244     this.data = grunt.config([name, target]);
245     // Expose normalized files object.
246     this.files = task.normalizeMultiTaskFiles(this.data, target);
247     // Expose normalized, flattened, uniqued array of src files.
248     Object.defineProperty(this, 'filesSrc', {
249       enumerable: true,
250       get: function() {
251         return grunt.util._(this.files).chain().map('src').flatten().uniq().value();
252       }.bind(this)
253     });
254     // Call original task function, passing in the target and any other args.
255     return fn.apply(this, this.args);
256   });
257
258   thisTask = task._tasks[name];
259   thisTask.multi = true;
260 };
261
262 // Init tasks don't require properties in config, and as such will preempt
263 // config loading errors.
264 task.registerInitTask = function(name, info, fn) {
265   task.registerTask(name, info, fn);
266   task._tasks[name].init = true;
267 };
268
269 // Override built-in renameTask to use the registry.
270 task.renameTask = function(oldname, newname) {
271   var result;
272   try {
273     // Actually rename task.
274     result = parent.renameTask.apply(task, arguments);
275     // Add and remove task.
276     registry.untasks.push(oldname);
277     registry.tasks.push(newname);
278     // Return result.
279     return result;
280   } catch (e) {
281     grunt.log.error(e.message);
282   }
283 };
284
285 // If a property wasn't passed, run all task targets in turn.
286 task.runAllTargets = function(taskname, args) {
287   // Get an array of sub-property keys under the given config object.
288   var targets = Object.keys(grunt.config.getRaw(taskname) || {});
289   // Remove invalid target properties.
290   targets = targets.filter(isValidMultiTaskTarget);
291   // Fail if there are no actual properties to iterate over.
292   if (targets.length === 0) {
293     grunt.log.error('No "' + taskname + '" targets found.');
294     return false;
295   }
296   // Iterate over all targets, running a task for each.
297   targets.forEach(function(target) {
298     // Be sure to pass in any additionally specified args.
299     task.run([taskname, target].concat(args || []).join(':'));
300   });
301 };
302
303 // Load tasks and handlers from a given tasks file.
304 var loadTaskStack = [];
305 function loadTask(filepath) {
306   // In case this was called recursively, save registry for later.
307   loadTaskStack.push(registry);
308   // Reset registry.
309   registry = {tasks: [], untasks: [], meta: {info: lastInfo, filepath: filepath}};
310   var filename = path.basename(filepath);
311   var msg = 'Loading "' + filename + '" tasks...';
312   var regCount = 0;
313   var fn;
314   try {
315     // Load taskfile.
316     fn = require(path.resolve(filepath));
317     if (typeof fn === 'function') {
318       fn.call(grunt, grunt);
319     }
320     grunt.verbose.write(msg).ok();
321     // Log registered/renamed/unregistered tasks.
322     ['un', ''].forEach(function(prefix) {
323       var list = grunt.util._.chain(registry[prefix + 'tasks']).uniq().sort().value();
324       if (list.length > 0) {
325         regCount++;
326         grunt.verbose.writeln((prefix ? '- ' : '+ ') + grunt.log.wordlist(list));
327       }
328     });
329     if (regCount === 0) {
330       grunt.verbose.warn('No tasks were registered or unregistered.');
331     }
332   } catch (e) {
333     // Something went wrong.
334     grunt.log.write(msg).error().verbose.error(e.stack).or.error(e);
335   }
336   // Restore registry.
337   registry = loadTaskStack.pop() || {};
338 }
339
340 // Log a message when loading tasks.
341 function loadTasksMessage(info) {
342   // Only keep track of names of top-level loaded tasks and collections,
343   // not sub-tasks.
344   if (loadTaskDepth === 0) { lastInfo = info; }
345   grunt.verbose.subhead('Registering ' + info + ' tasks.');
346 }
347
348 // Load tasks and handlers from a given directory.
349 function loadTasks(tasksdir) {
350   try {
351     var files = grunt.file.glob.sync('*.{js,coffee}', {cwd: tasksdir, maxDepth: 1});
352     // Load tasks from files.
353     files.forEach(function(filename) {
354       loadTask(path.join(tasksdir, filename));
355     });
356   } catch (e) {
357     grunt.log.verbose.error(e.stack).or.error(e);
358   }
359 }
360
361 // Load tasks and handlers from a given directory.
362 task.loadTasks = function(tasksdir) {
363   loadTasksMessage('"' + tasksdir + '"');
364   if (grunt.file.exists(tasksdir)) {
365     loadTasks(tasksdir);
366   } else {
367     grunt.log.error('Tasks directory "' + tasksdir + '" not found.');
368   }
369 };
370
371 // Load tasks and handlers from a given locally-installed Npm module (installed
372 // relative to the base dir).
373 task.loadNpmTasks = function(name) {
374   loadTasksMessage('"' + name + '" local Npm module');
375   var root = path.resolve('node_modules');
376   var pkgfile = path.join(root, name, 'package.json');
377   var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []};
378
379   // Process collection plugins.
380   if (pkg.keywords && pkg.keywords.indexOf('gruntcollection') !== -1) {
381     loadTaskDepth++;
382     Object.keys(pkg.dependencies).forEach(function(depName) {
383       // Npm sometimes pulls dependencies out if they're shared, so find
384       // upwards if not found locally.
385       var filepath = grunt.file.findup('node_modules/' + depName, {
386         cwd: path.resolve('node_modules', name),
387         nocase: true
388       });
389       if (filepath) {
390         // Load this task plugin recursively.
391         task.loadNpmTasks(path.relative(root, filepath));
392       }
393     });
394     loadTaskDepth--;
395     return;
396   }
397
398   // Process task plugins.
399   var tasksdir = path.join(root, name, 'tasks');
400   if (grunt.file.exists(tasksdir)) {
401     loadTasks(tasksdir);
402   } else {
403     grunt.log.error('Local Npm module "' + name + '" not found. Is it installed?');
404   }
405 };
406
407 // Initialize tasks.
408 task.init = function(tasks, options) {
409   if (!options) { options = {}; }
410
411   // Were only init tasks specified?
412   var allInit = tasks.length > 0 && tasks.every(function(name) {
413     var obj = task._taskPlusArgs(name).task;
414     return obj && obj.init;
415   });
416
417   // Get any local Gruntfile or tasks that might exist. Use --gruntfile override
418   // if specified, otherwise search the current directory or any parent.
419   var gruntfile, msg;
420   if (allInit || options.gruntfile === false) {
421     gruntfile = null;
422   } else {
423     gruntfile = grunt.option('gruntfile') ||
424       grunt.file.findup('Gruntfile.{js,coffee}', {nocase: true});
425     msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
426   }
427
428   if (options.gruntfile === false) {
429     // Grunt was run as a lib with {gruntfile: false}.
430   } else if (gruntfile && grunt.file.exists(gruntfile)) {
431     grunt.verbose.writeln().write(msg).ok();
432     // Change working directory so that all paths are relative to the
433     // Gruntfile's location (or the --base option, if specified).
434     process.chdir(grunt.option('base') || path.dirname(gruntfile));
435     // Load local tasks, if the file exists.
436     loadTasksMessage('Gruntfile');
437     loadTask(gruntfile);
438   } else if (options.help || allInit) {
439     // Don't complain about missing Gruntfile.
440   } else if (grunt.option('gruntfile')) {
441     // If --config override was specified and it doesn't exist, complain.
442     grunt.log.writeln().write(msg).error();
443     grunt.fatal('Unable to find "' + gruntfile + '" Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
444   } else if (!grunt.option('help')) {
445     grunt.verbose.writeln().write(msg).error();
446     grunt.log.writelns(
447       'A valid Gruntfile could not be found. Please see the getting ' +
448       'started guide for more information on how to configure grunt: ' +
449       'http://gruntjs.com/getting-started'
450     );
451     grunt.fatal('Unable to find Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
452   }
453
454   // Load all user-specified --npm tasks.
455   (grunt.option('npm') || []).map(String).forEach(task.loadNpmTasks);
456   // Load all user-specified --tasks.
457   (grunt.option('tasks') || []).map(String).forEach(task.loadTasks);
458 };