source: Dev/trunk/node_modules/grunt/lib/util/task.js @ 516

Last change on this file since 516 was 516, checked in by hendrikvanantwerpen, 11 years ago

Enable deployment with Grunt.

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