text: /*
   * grunt-contrib-watch
   * http://gruntjs.com/
   *
   * Copyright (c) 2013 "Cowboy" Ben Alman, contributors
   * Licensed under the MIT license.
   */
  
  var path = require('path');
  var Gaze = require('gaze').Gaze;
  var waiting = 'Waiting...';
  var changedFiles = Object.create(null);
  var watchers = [];
  
  module.exports = function(grunt) {
    'use strict';
  
    var taskrun = require('./lib/taskrunner')(grunt);
  
    // Default date format logged
    var dateFormat = function(time) {
      grunt.log.writeln(String(
        'Completed in ' +
        time.toFixed(3) +
        's at ' +
        (new Date()).toString()
      ).cyan + ' - ' + waiting);
    };
  
    // When task runner has started
    taskrun.on('start', function() {
      grunt.log.ok();
      Object.keys(changedFiles).forEach(function(filepath) {
        // Log which file has changed, and how.
        grunt.log.ok('File "' + filepath + '" ' + changedFiles[filepath] + '.');
      });
      grunt.log.writeln();
      // Reset changedFiles
      changedFiles = Object.create(null);
    });
  
    // When task runner has ended
    taskrun.on('end', function(time) {
      if (time > 0) {
        dateFormat(time);
      }
    });
  
    // When a task run has been interrupted
    taskrun.on('interrupt', function() {
      grunt.log.writeln('').write('Scheduled tasks have been interrupted...'.yellow);
    });
  
    // When taskrun is reloaded
    taskrun.on('reload', function() {
      taskrun.clearRequireCache(Object.keys(changedFiles));
      grunt.log.writeln('').writeln('Reloading watch config...'.cyan);
    });
  
    grunt.registerTask('watch', 'Run predefined tasks whenever watched files change.', function(target) {
      var self = this;
      var name = self.name || 'watch';
  
      // Close any previously opened watchers
      watchers.forEach(function(watcher) {
        watcher.close();
      });
      watchers = [];
  
      // Never gonna give you up, never gonna let you down
      if (grunt.config([name, 'options', 'forever']) !== false) {
        taskrun.forever();
      }
  
      // If a custom dateFormat function
      var df = grunt.config([name, 'options', 'dateFormat']);
      if (typeof df === 'function') {
        dateFormat = df;
      }
  
      if (taskrun.running === false) { grunt.log.write(waiting); }
  
      // initialize taskrun
      var targets = taskrun.init(name, {target: target});
  
      targets.forEach(function(target, i) {
        if (typeof target.files === 'string') { target.files = [target.files]; }
  
        // Process into raw patterns
        var patterns = grunt.util._.chain(target.files).flatten().map(function(pattern) {
          return grunt.config.process(pattern);
        }).value();
  
        // Validate the event option
        if (typeof target.options.event === 'string') {
          target.options.event = [target.options.event];
        }
  
        // Create watcher per target
        watchers.push(new Gaze(patterns, target.options, function(err) {
          if (err) {
            if (typeof err === 'string') { err = new Error(err); }
            grunt.log.writeln('ERROR'.red);
            grunt.fatal(err);
            return taskrun.done();
          }
  
          // Log all watched files with --verbose set
          if (grunt.option('verbose')) {
            var watched = this.watched();
            Object.keys(watched).forEach(function(watchedDir) {
              watched[watchedDir].forEach(function(watchedFile) {
                grunt.log.writeln('Watching ' + path.relative(process.cwd(), watchedFile) + ' for changes.');
              });
            });
          }
  
          // On changed/added/deleted
          this.on('all', function(status, filepath) {
  
            // Skip events not specified
            if (!grunt.util._.contains(target.options.event, 'all') &&
                !grunt.util._.contains(target.options.event, status)) {
              return;
            }
  
            filepath = path.relative(process.cwd(), filepath);
  
            // Skip empty filepaths
            if (filepath === '') {
              return;
            }
  
            // If Gruntfile.js changed, reload self task
            if (/gruntfile\.(js|coffee)/i.test(filepath)) {
              taskrun.reload = true;
            }
  
            // Emit watch events if anyone is listening
            if (grunt.event.listeners('watch').length > 0) {
              grunt.event.emit('watch', status, filepath, target.name);
            }
  
            // Group changed files only for display
            changedFiles[filepath] = status;
  
            // Add changed files to the target
            if (taskrun.targets[target.name]) {
              taskrun.targets[target.name].changedFiles = Object.create(null);
              taskrun.targets[target.name].changedFiles[filepath] = status;
            }
  
            // Queue the target
            if (taskrun.queue.indexOf(target.name) === -1) {
              taskrun.queue.push(target.name);
            }
  
            // Run the tasks
            taskrun.run();
          });
  
          // On watcher error
          this.on('error', function(err) {
            if (typeof err === 'string') { err = new Error(err); }
            grunt.log.error(err.message);
          });
        }));
      });
  
    });
  };
  