Added the Search API Synonym module to deal specifically with licence and license...
[yaffs-website] / node_modules / grunt / lib / util / task.js
1 (function(exports) {
2
3   'use strict';
4
5   var grunt = require('../grunt');
6
7   // Construct-o-rama.
8   function Task() {
9     // Information about the currently-running task.
10     this.current = {};
11     // Tasks.
12     this._tasks = {};
13     // Task queue.
14     this._queue = [];
15     // Queue placeholder (for dealing with nested tasks).
16     this._placeholder = {placeholder: true};
17     // Queue marker (for clearing the queue programmatically).
18     this._marker = {marker: true};
19     // Options.
20     this._options = {};
21     // Is the queue running?
22     this._running = false;
23     // Success status of completed tasks.
24     this._success = {};
25   }
26
27   // Expose the constructor function.
28   exports.Task = Task;
29
30   // Create a new Task instance.
31   exports.create = function() {
32     return new Task();
33   };
34
35   // If the task runner is running or an error handler is not defined, throw
36   // an exception. Otherwise, call the error handler directly.
37   Task.prototype._throwIfRunning = function(obj) {
38     if (this._running || !this._options.error) {
39       // Throw an exception that the task runner will catch.
40       throw obj;
41     } else {
42       // Not inside the task runner. Call the error handler and abort.
43       this._options.error.call({name: null}, obj);
44     }
45   };
46
47   // Register a new task.
48   Task.prototype.registerTask = function(name, info, fn) {
49     // If optional "info" string is omitted, shuffle arguments a bit.
50     if (fn == null) {
51       fn = info;
52       info = null;
53     }
54     // String or array of strings was passed instead of fn.
55     var tasks;
56     if (typeof fn !== 'function') {
57       // Array of task names.
58       tasks = this.parseArgs([fn]);
59       // This task function just runs the specified tasks.
60       fn = this.run.bind(this, fn);
61       fn.alias = true;
62       // Generate an info string if one wasn't explicitly passed.
63       if (!info) {
64         info = 'Alias for "' + tasks.join('", "') + '" task' +
65           (tasks.length === 1 ? '' : 's') + '.';
66       }
67     } else if (!info) {
68       info = 'Custom task.';
69     }
70     // Add task into cache.
71     this._tasks[name] = {name: name, info: info, fn: fn};
72     // Make chainable!
73     return this;
74   };
75
76   // Is the specified task an alias?
77   Task.prototype.isTaskAlias = function(name) {
78     return !!this._tasks[name].fn.alias;
79   };
80
81   // Has the specified task been registered?
82   Task.prototype.exists = function(name) {
83     return name in this._tasks;
84   };
85
86   // Rename a task. This might be useful if you want to override the default
87   // behavior of a task, while retaining the old name. This is a billion times
88   // easier to implement than some kind of in-task "super" functionality.
89   Task.prototype.renameTask = function(oldname, newname) {
90     if (!this._tasks[oldname]) {
91       throw new Error('Cannot rename missing "' + oldname + '" task.');
92     }
93     // Rename task.
94     this._tasks[newname] = this._tasks[oldname];
95     // Update name property of task.
96     this._tasks[newname].name = newname;
97     // Remove old name.
98     delete this._tasks[oldname];
99     // Make chainable!
100     return this;
101   };
102
103   // Argument parsing helper. Supports these signatures:
104   //  fn('foo')                 // ['foo']
105   //  fn('foo', 'bar', 'baz')   // ['foo', 'bar', 'baz']
106   //  fn(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz']
107   Task.prototype.parseArgs = function(args) {
108     // Return the first argument if it's an array, otherwise return an array
109     // of all arguments.
110     return Array.isArray(args[0]) ? args[0] : [].slice.call(args);
111   };
112
113   // Split a colon-delimited string into an array, unescaping (but not
114   // splitting on) any \: escaped colons.
115   Task.prototype.splitArgs = function(str) {
116     if (!str) { return []; }
117     // Store placeholder for \\ followed by \:
118     str = str.replace(/\\\\/g, '\uFFFF').replace(/\\:/g, '\uFFFE');
119     // Split on :
120     return str.split(':').map(function(s) {
121       // Restore place-held : followed by \\
122       return s.replace(/\uFFFE/g, ':').replace(/\uFFFF/g, '\\');
123     });
124   };
125
126   // Given a task name, determine which actual task will be called, and what
127   // arguments will be passed into the task callback. "foo" -> task "foo", no
128   // args. "foo:bar:baz" -> task "foo:bar:baz" with no args (if "foo:bar:baz"
129   // task exists), otherwise task "foo:bar" with arg "baz" (if "foo:bar" task
130   // exists), otherwise task "foo" with args "bar" and "baz".
131   Task.prototype._taskPlusArgs = function(name) {
132     // Get task name / argument parts.
133     var parts = this.splitArgs(name);
134     // Start from the end, not the beginning!
135     var i = parts.length;
136     var task;
137     do {
138       // Get a task.
139       task = this._tasks[parts.slice(0, i).join(':')];
140       // If the task doesn't exist, decrement `i`, and if `i` is greater than
141       // 0, repeat.
142     } while (!task && --i > 0);
143     // Just the args.
144     var args = parts.slice(i);
145     // Maybe you want to use them as flags instead of as positional args?
146     var flags = {};
147     args.forEach(function(arg) { flags[arg] = true; });
148     // The task to run and the args to run it with.
149     return {task: task, nameArgs: name, args: args, flags: flags};
150   };
151
152   // Append things to queue in the correct spot.
153   Task.prototype._push = function(things) {
154     // Get current placeholder index.
155     var index = this._queue.indexOf(this._placeholder);
156     if (index === -1) {
157       // No placeholder, add task+args objects to end of queue.
158       this._queue = this._queue.concat(things);
159     } else {
160       // Placeholder exists, add task+args objects just before placeholder.
161       [].splice.apply(this._queue, [index, 0].concat(things));
162     }
163   };
164
165   // Enqueue a task.
166   Task.prototype.run = function() {
167     // Parse arguments into an array, returning an array of task+args objects.
168     var things = this.parseArgs(arguments).map(this._taskPlusArgs, this);
169     // Throw an exception if any tasks weren't found.
170     var fails = things.filter(function(thing) { return !thing.task; });
171     if (fails.length > 0) {
172       this._throwIfRunning(new Error('Task "' + fails[0].nameArgs + '" not found.'));
173       return this;
174     }
175     // Append things to queue in the correct spot.
176     this._push(things);
177     // Make chainable!
178     return this;
179   };
180
181   // Add a marker to the queue to facilitate clearing it programmatically.
182   Task.prototype.mark = function() {
183     this._push(this._marker);
184     // Make chainable!
185     return this;
186   };
187
188   // Run a task function, handling this.async / return value.
189   Task.prototype.runTaskFn = function(context, fn, done, asyncDone) {
190     // Async flag.
191     var async = false;
192
193     // Update the internal status object and run the next task.
194     var complete = function(success) {
195       var err = null;
196       if (success === false) {
197         // Since false was passed, the task failed generically.
198         err = new Error('Task "' + context.nameArgs + '" failed.');
199       } else if (success instanceof Error || {}.toString.call(success) === '[object Error]') {
200         // An error object was passed, so the task failed specifically.
201         err = success;
202         success = false;
203       } else {
204         // The task succeeded.
205         success = true;
206       }
207       // The task has ended, reset the current task object.
208       this.current = {};
209       // A task has "failed" only if it returns false (async) or if the
210       // function returned by .async is passed false.
211       this._success[context.nameArgs] = success;
212       // If task failed, call error handler.
213       if (!success && this._options.error) {
214         this._options.error.call({name: context.name, nameArgs: context.nameArgs}, err);
215       }
216       // only call done async if explicitly requested to
217       // see: https://github.com/gruntjs/grunt/pull/1026
218       if (asyncDone) {
219         process.nextTick(function() {
220           done(err, success);
221         });
222       } else {
223         done(err, success);
224       }
225     }.bind(this);
226
227     // When called, sets the async flag and returns a function that can
228     // be used to continue processing the queue.
229     context.async = function() {
230       async = true;
231       // The returned function should execute asynchronously in case
232       // someone tries to do this.async()(); inside a task (WTF).
233       return grunt.util._.once(function(success) {
234         setTimeout(function() { complete(success); }, 1);
235       });
236     };
237
238     // Expose some information about the currently-running task.
239     this.current = context;
240
241     try {
242       // Get the current task and run it, setting `this` inside the task
243       // function to be something useful.
244       var success = fn.call(context);
245       // If the async flag wasn't set, process the next task in the queue.
246       if (!async) {
247         complete(success);
248       }
249     } catch (err) {
250       complete(err);
251     }
252   };
253
254   // Begin task queue processing. Ie. run all tasks.
255   Task.prototype.start = function(opts) {
256     if (!opts) {
257       opts = {};
258     }
259     // Abort if already running.
260     if (this._running) { return false; }
261     // Actually process the next task.
262     var nextTask = function() {
263       // Get next task+args object from queue.
264       var thing;
265       // Skip any placeholders or markers.
266       do {
267         thing = this._queue.shift();
268       } while (thing === this._placeholder || thing === this._marker);
269       // If queue was empty, we're all done.
270       if (!thing) {
271         this._running = false;
272         if (this._options.done) {
273           this._options.done();
274         }
275         return;
276       }
277       // Add a placeholder to the front of the queue.
278       this._queue.unshift(this._placeholder);
279
280       // Expose some information about the currently-running task.
281       var context = {
282         // The current task name plus args, as-passed.
283         nameArgs: thing.nameArgs,
284         // The current task name.
285         name: thing.task.name,
286         // The current task arguments.
287         args: thing.args,
288         // The current arguments, available as named flags.
289         flags: thing.flags
290       };
291
292       // Actually run the task function (handling this.async, etc)
293       this.runTaskFn(context, function() {
294         return thing.task.fn.apply(this, this.args);
295       }, nextTask, !!opts.asyncDone);
296
297     }.bind(this);
298
299     // Update flag.
300     this._running = true;
301     // Process the next task.
302     nextTask();
303   };
304
305   // Clear remaining tasks from the queue.
306   Task.prototype.clearQueue = function(options) {
307     if (!options) { options = {}; }
308     if (options.untilMarker) {
309       this._queue.splice(0, this._queue.indexOf(this._marker) + 1);
310     } else {
311       this._queue = [];
312     }
313     // Make chainable!
314     return this;
315   };
316
317   // Test to see if all of the given tasks have succeeded.
318   Task.prototype.requires = function() {
319     this.parseArgs(arguments).forEach(function(name) {
320       var success = this._success[name];
321       if (!success) {
322         throw new Error('Required task "' + name +
323           '" ' + (success === false ? 'failed' : 'must be run first') + '.');
324       }
325     }.bind(this));
326   };
327
328   // Override default options.
329   Task.prototype.options = function(options) {
330     Object.keys(options).forEach(function(name) {
331       this._options[name] = options[name];
332     }.bind(this));
333   };
334
335 }(typeof exports === 'object' && exports || this));