Initial commit
[yaffs-website] / node_modules / grunt-contrib-watch / tasks / lib / taskrunner.js
1 /*
2  * grunt-contrib-watch
3  * http://gruntjs.com/
4  *
5  * Copyright (c) 2016 "Cowboy" Ben Alman, contributors
6  * Licensed under the MIT license.
7  */
8
9 'use strict';
10
11 var path = require('path');
12 var EE = require('events').EventEmitter;
13 var util = require('util');
14 var _ = require('lodash');
15 var async = require('async');
16
17 // Track which targets to run after reload
18 var reloadTargets = [];
19
20 // A default target name for config where targets are not used (keep this unique)
21 var defaultTargetName = '_$_default_$_';
22
23 module.exports = function(grunt) {
24
25   var TaskRun = require('./taskrun')(grunt);
26   var livereload = require('./livereload')(grunt);
27
28   function Runner() {
29     EE.call(this);
30     // Name of the task
31     this.name = 'watch';
32     // Options for the runner
33     this.options = {};
34     // Function to close the task
35     this.done = function() {};
36     // Targets available to task run
37     this.targets = Object.create(null);
38     // The queue of task runs
39     this.queue = [];
40     // Whether we're actively running tasks
41     this.running = false;
42     // If a nospawn task has ran (and needs the watch to restart)
43     this.nospawn = false;
44     // Set to true before run() to reload task
45     this.reload = false;
46     // For re-queuing arguments with the task that originally ran this
47     this.nameArgs = [];
48     // A list of changed files to feed to task runs for livereload
49     this.changedFiles = Object.create(null);
50   }
51   util.inherits(Runner, EE);
52
53   // Init a task for taskrun
54   Runner.prototype.init = function init(name, defaults, done) {
55     var self = this;
56
57     self.name = name || grunt.task.current.name || 'watch';
58     self.options = self._options(grunt.config([self.name, 'options']) || {}, defaults || {});
59     self.reload = false;
60     self.nameArgs = grunt.task.current.nameArgs ? grunt.task.current.nameArgs : self.name;
61
62     // Normalize cwd option
63     if (typeof self.options.cwd === 'string') {
64       self.options.cwd = {files: self.options.cwd, spawn: self.options.cwd};
65     }
66
67     // Function to call when closing the task
68     self.done = done || grunt.task.current.async();
69
70     // If a default livereload server for all targets
71     // Use task level unless target level overrides
72     var taskLRConfig = grunt.config([self.name, 'options', 'livereload']);
73     if (self.options.target && taskLRConfig) {
74       var targetLRConfig = grunt.config([self.name, self.options.target, 'options', 'livereload']);
75       if (targetLRConfig) {
76         // Dont use task level as target level will be used instead
77         taskLRConfig = false;
78       }
79     }
80     if (taskLRConfig) {
81       self.livereload = livereload(taskLRConfig);
82     }
83
84     // Return the targets normalized
85     var targets = self._getTargets(self.name);
86
87     if (self.running) {
88       // If previously running, complete the last run
89       self.complete();
90     } else if (reloadTargets.length > 0) {
91       // If not previously running but has items in the queue, needs run
92       self.queue = reloadTargets;
93       reloadTargets = [];
94       self.run();
95     } else {
96       if (!self.hadError) {
97         // Check whether target's tasks should run at start w/ atBegin option
98         self.queue = targets.filter(function(tr) {
99           return tr.options.atBegin === true && tr.tasks.length > 0;
100         }).map(function(tr) {
101           return tr.name;
102         });
103       } else {
104         // There was an error in atBegin task, we can't re-run it, as this would
105         // create an infinite loop of failing tasks
106         // See https://github.com/gruntjs/grunt-contrib-watch/issues/169
107         self.queue = [];
108         self.hadError = false;
109       }
110       if (self.queue.length > 0) {
111         self.run();
112       }
113     }
114
115     return targets;
116   };
117
118   // Normalize targets from config
119   Runner.prototype._getTargets = function _getTargets(name) {
120     var self = this;
121
122     grunt.task.current.requiresConfig(name);
123     var config = grunt.config(name);
124     var onlyTarget = self.options.target ? self.options.target : false;
125
126     var targets = (onlyTarget ? [onlyTarget] : Object.keys(config)).filter(function(key) {
127       if (key === 'options') {
128         return false;
129       }
130       return typeof config[key] !== 'string' && !Array.isArray(config[key]);
131     }).map(function(target) {
132       // Fail if any required config properties have been omitted
133       grunt.task.current.requiresConfig([name, target, 'files']);
134       var cfg = grunt.config([name, target]);
135       cfg.name = target;
136       cfg.options = self._options(cfg.options || {}, self.options);
137       self.add(cfg);
138       return cfg;
139     }, self);
140
141     // Allow "basic" non-target format
142     if (typeof config.files === 'string' || Array.isArray(config.files)) {
143       var cfg = {
144         files: config.files,
145         tasks: config.tasks,
146         name: defaultTargetName,
147         options: self._options(config.options || {}, self.options)
148       };
149       targets.push(cfg);
150       self.add(cfg);
151     }
152
153     return targets;
154   };
155
156   // Default options
157   Runner.prototype._options = function _options() {
158     var args = Array.prototype.slice.call(arguments).concat({
159       // The cwd to spawn within
160       cwd: process.cwd(),
161       // Additional cli args to append when spawning
162       cliArgs: _.without.apply(null, [[].slice.call(process.argv, 2)].concat(grunt.cli.tasks)),
163       interrupt: false,
164       nospawn: false,
165       spawn: true,
166       atBegin: false,
167       event: ['all'],
168       target: null
169     });
170     return _.defaults.apply(_, args);
171   };
172
173   // Run the current queue of task runs
174   Runner.prototype.run = _.debounce(function run() {
175     var self = this;
176     if (self.queue.length < 1) {
177       self.running = false;
178       return;
179     }
180
181     // Re-grab task options in case they changed between runs
182     self.options = self._options(grunt.config([self.name, 'options']) || {}, self.options);
183
184     // If we should interrupt
185     if (self.running === true) {
186       var shouldInterrupt = true;
187       self.queue.forEach(function(name) {
188         var tr = self.targets[name];
189         if (tr && tr.options.interrupt !== true) {
190           shouldInterrupt = false;
191           return false;
192         }
193       });
194       if (shouldInterrupt === true) {
195         self.interrupt();
196       } else {
197         // Dont interrupt the tasks running
198         return;
199       }
200     }
201
202     // If we should reload
203     if (self.reload) {
204       return self.reloadTask();
205     }
206
207     // Trigger that tasks runs have started
208     self.emit('start');
209     self.running = true;
210
211     // Run each target
212     var shouldComplete = true;
213     async.forEachSeries(self.queue, function(name, next) {
214       var tr = self.targets[name];
215       if (!tr) {
216         return next();
217       }
218
219       // Re-grab options in case they changed between runs
220       tr.options = self._options(grunt.config([self.name, name, 'options']) || {}, tr.options, self.options);
221
222       if (tr.options.spawn === false || tr.options.nospawn === true) {
223         shouldComplete = false;
224       }
225       tr.run(next);
226     }, function() {
227       if (shouldComplete) {
228         self.complete();
229       } else {
230         grunt.task.mark().run(self.nameArgs);
231         self.done();
232       }
233     });
234   }, 250);
235
236   // Push targets onto the queue
237   Runner.prototype.add = function add(target) {
238     var self = this;
239     if (!this.targets[target.name || 0]) {
240
241       // Private method for getting latest config for a watch target
242       target._getConfig = function(name) {
243         var cfgPath = [self.name];
244         if (target.name !== defaultTargetName) {
245           cfgPath.push(target.name);
246         }
247         if (name) {
248           cfgPath.push(name);
249         }
250         return grunt.config(cfgPath);
251       };
252
253       // Create a new TaskRun instance
254       var tr = new TaskRun(target);
255
256       // Add livereload to task runs
257       // Get directly from config as task level options are merged.
258       // We only want a single default LR server and then
259       // allow each target to override their own.
260       var lrconfig = grunt.config([this.name, target.name || 0, 'options', 'livereload']);
261       if (lrconfig) {
262         tr.livereload = livereload(lrconfig);
263       } else if (this.livereload && lrconfig !== false) {
264         tr.livereload = this.livereload;
265       }
266
267       return this.targets[tr.name] = tr;
268     }
269     return false;
270   };
271
272   // Do this when queued task runs have completed/scheduled
273   Runner.prototype.complete = function complete() {
274     var self = this;
275     if (self.running === false) {
276       return;
277     }
278     self.running = false;
279     var time = 0;
280     for (var i = 0, len = self.queue.length; i < len; ++i) {
281       var name = self.queue[i];
282       var target = self.targets[name];
283       if (!target) {
284         return;
285       }
286       if (target.startedAt !== false) {
287         time += target.complete();
288         self.queue.splice(i--, 1);
289         len--;
290
291         // if we're just livereloading and no tasks
292         // it can happen too fast and we dont report it
293         if (target.options.livereload && target.tasks.length < 1) {
294           time += 0.0001;
295         }
296       }
297     }
298     var elapsed = (time > 0) ? Number(time / 1000) : 0;
299     self.changedFiles = Object.create(null);
300     self.emit('end', elapsed);
301   };
302
303   // Run through completing every target in the queue
304   Runner.prototype._completeQueue = function _completeQueue() {
305     var self = this;
306     self.queue.forEach(function(name) {
307       var target = self.targets[name];
308       if (!target) {
309         return;
310       }
311       target.complete();
312     });
313   };
314
315   // Interrupt the running tasks
316   Runner.prototype.interrupt = function interrupt() {
317     var self = this;
318     self._completeQueue();
319     grunt.task.clearQueue();
320     self.emit('interrupt');
321   };
322
323   // Attempt to make this task run forever
324   Runner.prototype.forever = function forever() {
325     var self = this;
326     function rerun() {
327       // Clear queue and rerun to prevent failing
328       self._completeQueue();
329       grunt.task.clearQueue();
330       grunt.task.run(self.nameArgs);
331       self.running = false;
332       // Mark that there was an error and we needed to rerun
333       self.hadError = true;
334     }
335     grunt.fail.forever_warncount = 0;
336     grunt.fail.forever_errorcount = 0;
337     grunt.warn = grunt.fail.warn = function(e) {
338       grunt.fail.forever_warncount ++;
339       var message = typeof e === 'string' ? e : e.message;
340       grunt.log.writeln(('Warning: ' + message).yellow);
341       if (!grunt.option('force')) {
342         rerun();
343       }
344     };
345     grunt.fatal = grunt.fail.fatal = function(e) {
346       grunt.fail.forever_errorcount ++;
347       var message = typeof e === 'string' ? e : e.message;
348       grunt.log.writeln(('Fatal error: ' + message).red);
349       rerun();
350     };
351   };
352
353   // Clear the require cache for all passed filepaths.
354   Runner.prototype.clearRequireCache = function() {
355     // If a non-string argument is passed, it's an array of filepaths, otherwise
356     // each filepath is passed individually.
357     var filepaths = typeof arguments[0] !== 'string' ? arguments[0] : Array.prototype.slice(arguments);
358     // For each filepath, clear the require cache, if necessary.
359     filepaths.forEach(function(filepath) {
360       var abspath = path.resolve(filepath);
361       if (require.cache[abspath]) {
362         grunt.verbose.write('Clearing require cache for "' + filepath + '" file...').ok();
363         delete require.cache[abspath];
364       }
365     });
366   };
367
368   // Reload this watch task, like when a Gruntfile is edited
369   Runner.prototype.reloadTask = function() {
370     var self = this;
371     // Which targets to run after reload
372     reloadTargets = self.queue;
373     self.emit('reload', reloadTargets);
374
375     // Re-init the watch task config
376     grunt.task.init([self.name]);
377
378     // Complete all running tasks
379     self._completeQueue();
380
381     // Run the watch task again
382     grunt.task.run(self.nameArgs);
383     self.done();
384   };
385
386   return new Runner();
387 };