Version 1
[yaffs-website] / node_modules / grunt / lib / util / task.js
diff --git a/node_modules/grunt/lib/util/task.js b/node_modules/grunt/lib/util/task.js
new file mode 100644 (file)
index 0000000..d39ce1b
--- /dev/null
@@ -0,0 +1,335 @@
+(function(exports) {
+
+  'use strict';
+
+  var grunt = require('../grunt');
+
+  // Construct-o-rama.
+  function Task() {
+    // Information about the currently-running task.
+    this.current = {};
+    // Tasks.
+    this._tasks = {};
+    // Task queue.
+    this._queue = [];
+    // Queue placeholder (for dealing with nested tasks).
+    this._placeholder = {placeholder: true};
+    // Queue marker (for clearing the queue programmatically).
+    this._marker = {marker: true};
+    // Options.
+    this._options = {};
+    // Is the queue running?
+    this._running = false;
+    // Success status of completed tasks.
+    this._success = {};
+  }
+
+  // Expose the constructor function.
+  exports.Task = Task;
+
+  // Create a new Task instance.
+  exports.create = function() {
+    return new Task();
+  };
+
+  // If the task runner is running or an error handler is not defined, throw
+  // an exception. Otherwise, call the error handler directly.
+  Task.prototype._throwIfRunning = function(obj) {
+    if (this._running || !this._options.error) {
+      // Throw an exception that the task runner will catch.
+      throw obj;
+    } else {
+      // Not inside the task runner. Call the error handler and abort.
+      this._options.error.call({name: null}, obj);
+    }
+  };
+
+  // Register a new task.
+  Task.prototype.registerTask = function(name, info, fn) {
+    // If optional "info" string is omitted, shuffle arguments a bit.
+    if (fn == null) {
+      fn = info;
+      info = null;
+    }
+    // String or array of strings was passed instead of fn.
+    var tasks;
+    if (typeof fn !== 'function') {
+      // Array of task names.
+      tasks = this.parseArgs([fn]);
+      // This task function just runs the specified tasks.
+      fn = this.run.bind(this, fn);
+      fn.alias = true;
+      // Generate an info string if one wasn't explicitly passed.
+      if (!info) {
+        info = 'Alias for "' + tasks.join('", "') + '" task' +
+          (tasks.length === 1 ? '' : 's') + '.';
+      }
+    } else if (!info) {
+      info = 'Custom task.';
+    }
+    // Add task into cache.
+    this._tasks[name] = {name: name, info: info, fn: fn};
+    // Make chainable!
+    return this;
+  };
+
+  // Is the specified task an alias?
+  Task.prototype.isTaskAlias = function(name) {
+    return !!this._tasks[name].fn.alias;
+  };
+
+  // Has the specified task been registered?
+  Task.prototype.exists = function(name) {
+    return name in this._tasks;
+  };
+
+  // Rename a task. This might be useful if you want to override the default
+  // behavior of a task, while retaining the old name. This is a billion times
+  // easier to implement than some kind of in-task "super" functionality.
+  Task.prototype.renameTask = function(oldname, newname) {
+    if (!this._tasks[oldname]) {
+      throw new Error('Cannot rename missing "' + oldname + '" task.');
+    }
+    // Rename task.
+    this._tasks[newname] = this._tasks[oldname];
+    // Update name property of task.
+    this._tasks[newname].name = newname;
+    // Remove old name.
+    delete this._tasks[oldname];
+    // Make chainable!
+    return this;
+  };
+
+  // Argument parsing helper. Supports these signatures:
+  //  fn('foo')                 // ['foo']
+  //  fn('foo', 'bar', 'baz')   // ['foo', 'bar', 'baz']
+  //  fn(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz']
+  Task.prototype.parseArgs = function(args) {
+    // Return the first argument if it's an array, otherwise return an array
+    // of all arguments.
+    return Array.isArray(args[0]) ? args[0] : [].slice.call(args);
+  };
+
+  // Split a colon-delimited string into an array, unescaping (but not
+  // splitting on) any \: escaped colons.
+  Task.prototype.splitArgs = function(str) {
+    if (!str) { return []; }
+    // Store placeholder for \\ followed by \:
+    str = str.replace(/\\\\/g, '\uFFFF').replace(/\\:/g, '\uFFFE');
+    // Split on :
+    return str.split(':').map(function(s) {
+      // Restore place-held : followed by \\
+      return s.replace(/\uFFFE/g, ':').replace(/\uFFFF/g, '\\');
+    });
+  };
+
+  // Given a task name, determine which actual task will be called, and what
+  // arguments will be passed into the task callback. "foo" -> task "foo", no
+  // args. "foo:bar:baz" -> task "foo:bar:baz" with no args (if "foo:bar:baz"
+  // task exists), otherwise task "foo:bar" with arg "baz" (if "foo:bar" task
+  // exists), otherwise task "foo" with args "bar" and "baz".
+  Task.prototype._taskPlusArgs = function(name) {
+    // Get task name / argument parts.
+    var parts = this.splitArgs(name);
+    // Start from the end, not the beginning!
+    var i = parts.length;
+    var task;
+    do {
+      // Get a task.
+      task = this._tasks[parts.slice(0, i).join(':')];
+      // If the task doesn't exist, decrement `i`, and if `i` is greater than
+      // 0, repeat.
+    } while (!task && --i > 0);
+    // Just the args.
+    var args = parts.slice(i);
+    // Maybe you want to use them as flags instead of as positional args?
+    var flags = {};
+    args.forEach(function(arg) { flags[arg] = true; });
+    // The task to run and the args to run it with.
+    return {task: task, nameArgs: name, args: args, flags: flags};
+  };
+
+  // Append things to queue in the correct spot.
+  Task.prototype._push = function(things) {
+    // Get current placeholder index.
+    var index = this._queue.indexOf(this._placeholder);
+    if (index === -1) {
+      // No placeholder, add task+args objects to end of queue.
+      this._queue = this._queue.concat(things);
+    } else {
+      // Placeholder exists, add task+args objects just before placeholder.
+      [].splice.apply(this._queue, [index, 0].concat(things));
+    }
+  };
+
+  // Enqueue a task.
+  Task.prototype.run = function() {
+    // Parse arguments into an array, returning an array of task+args objects.
+    var things = this.parseArgs(arguments).map(this._taskPlusArgs, this);
+    // Throw an exception if any tasks weren't found.
+    var fails = things.filter(function(thing) { return !thing.task; });
+    if (fails.length > 0) {
+      this._throwIfRunning(new Error('Task "' + fails[0].nameArgs + '" not found.'));
+      return this;
+    }
+    // Append things to queue in the correct spot.
+    this._push(things);
+    // Make chainable!
+    return this;
+  };
+
+  // Add a marker to the queue to facilitate clearing it programmatically.
+  Task.prototype.mark = function() {
+    this._push(this._marker);
+    // Make chainable!
+    return this;
+  };
+
+  // Run a task function, handling this.async / return value.
+  Task.prototype.runTaskFn = function(context, fn, done, asyncDone) {
+    // Async flag.
+    var async = false;
+
+    // Update the internal status object and run the next task.
+    var complete = function(success) {
+      var err = null;
+      if (success === false) {
+        // Since false was passed, the task failed generically.
+        err = new Error('Task "' + context.nameArgs + '" failed.');
+      } else if (success instanceof Error || {}.toString.call(success) === '[object Error]') {
+        // An error object was passed, so the task failed specifically.
+        err = success;
+        success = false;
+      } else {
+        // The task succeeded.
+        success = true;
+      }
+      // The task has ended, reset the current task object.
+      this.current = {};
+      // A task has "failed" only if it returns false (async) or if the
+      // function returned by .async is passed false.
+      this._success[context.nameArgs] = success;
+      // If task failed, call error handler.
+      if (!success && this._options.error) {
+        this._options.error.call({name: context.name, nameArgs: context.nameArgs}, err);
+      }
+      // only call done async if explicitly requested to
+      // see: https://github.com/gruntjs/grunt/pull/1026
+      if (asyncDone) {
+        process.nextTick(function() {
+          done(err, success);
+        });
+      } else {
+        done(err, success);
+      }
+    }.bind(this);
+
+    // When called, sets the async flag and returns a function that can
+    // be used to continue processing the queue.
+    context.async = function() {
+      async = true;
+      // The returned function should execute asynchronously in case
+      // someone tries to do this.async()(); inside a task (WTF).
+      return grunt.util._.once(function(success) {
+        setTimeout(function() { complete(success); }, 1);
+      });
+    };
+
+    // Expose some information about the currently-running task.
+    this.current = context;
+
+    try {
+      // Get the current task and run it, setting `this` inside the task
+      // function to be something useful.
+      var success = fn.call(context);
+      // If the async flag wasn't set, process the next task in the queue.
+      if (!async) {
+        complete(success);
+      }
+    } catch (err) {
+      complete(err);
+    }
+  };
+
+  // Begin task queue processing. Ie. run all tasks.
+  Task.prototype.start = function(opts) {
+    if (!opts) {
+      opts = {};
+    }
+    // Abort if already running.
+    if (this._running) { return false; }
+    // Actually process the next task.
+    var nextTask = function() {
+      // Get next task+args object from queue.
+      var thing;
+      // Skip any placeholders or markers.
+      do {
+        thing = this._queue.shift();
+      } while (thing === this._placeholder || thing === this._marker);
+      // If queue was empty, we're all done.
+      if (!thing) {
+        this._running = false;
+        if (this._options.done) {
+          this._options.done();
+        }
+        return;
+      }
+      // Add a placeholder to the front of the queue.
+      this._queue.unshift(this._placeholder);
+
+      // Expose some information about the currently-running task.
+      var context = {
+        // The current task name plus args, as-passed.
+        nameArgs: thing.nameArgs,
+        // The current task name.
+        name: thing.task.name,
+        // The current task arguments.
+        args: thing.args,
+        // The current arguments, available as named flags.
+        flags: thing.flags
+      };
+
+      // Actually run the task function (handling this.async, etc)
+      this.runTaskFn(context, function() {
+        return thing.task.fn.apply(this, this.args);
+      }, nextTask, !!opts.asyncDone);
+
+    }.bind(this);
+
+    // Update flag.
+    this._running = true;
+    // Process the next task.
+    nextTask();
+  };
+
+  // Clear remaining tasks from the queue.
+  Task.prototype.clearQueue = function(options) {
+    if (!options) { options = {}; }
+    if (options.untilMarker) {
+      this._queue.splice(0, this._queue.indexOf(this._marker) + 1);
+    } else {
+      this._queue = [];
+    }
+    // Make chainable!
+    return this;
+  };
+
+  // Test to see if all of the given tasks have succeeded.
+  Task.prototype.requires = function() {
+    this.parseArgs(arguments).forEach(function(name) {
+      var success = this._success[name];
+      if (!success) {
+        throw new Error('Required task "' + name +
+          '" ' + (success === false ? 'failed' : 'must be run first') + '.');
+      }
+    }.bind(this));
+  };
+
+  // Override default options.
+  Task.prototype.options = function(options) {
+    Object.keys(options).forEach(function(name) {
+      this._options[name] = options[name];
+    }.bind(this));
+  };
+
+}(typeof exports === 'object' && exports || this));