docs: function proceedCopyTask(task, done) {
        var src                   = task.src;
        var dest                  = task.dest;
        var options               = task.options;
        var lazy                  = options.lazy;
        var transforms            = options.transforms.slice(0);
        var process               = options.process;
        var processContent        = options.processContent;
        var processContentExclude = options.processContentExclude;
        var stripBanners          = options.stripBanners;
        var mode                  = options.mode;
        var isConcat              = options.isConcat;
        var cache                 = options.cache;
        var readOnly              = options.readOnly;
        var statSrc, statDest;
  
        // Banner and footer if any and if not applyed during concatenation
        var banner                = isConcat ? false : options.banner || false;
        var footer                = isConcat ? false : options.footer || false;
  
        // Compatibility with grunt-contrib-copy
        // Support for the processContent function
        if(processContent && processContentExclude !== true) {
          transforms.push(function(src, dest) {
            var body = [], result;
  
            // Check for exclusion
            if( processContentExclude && grunt.file.isMatch(processContentExclude, src)) {
              return through2();
            }
  
            // Else concat content and execute processContent
            return through2(
              function (buf, enc, cb) {
                body.push(buf.toString());
                cb();
              },
              function (cb) {
                body   = body.join('');
                result = processContent( body, src, dest, _.extend({}, options));
                this.push(_.isString(result) ? result : body);
                cb();
            });
  
          });
        }
  
        // Compatibility with grunt-contrib-concat
        // Support for the process & stripBanners function
        if(process || stripBanners) {
          if (process === true) { process = {}; }
          transforms.push(function(src, dest) {
            // Else concat content and execute processContent
            var body = [], result;
  
            return through2(
              function (buf, enc, cb) {
                body.push(buf.toString());
                cb();
              },
              function end(cb) {
                result = body = body.join('');
                if (typeof process === 'function') {
                  result = process( body, src, dest, _.extend({}, options));
                } else if (process) {
                  result = grunt.template.process(body, process);
                }
  
                // Strip banners if requested.
                if (stripBanners) {
                  result = comment.stripBanner(result, stripBanners);
                }
                this.push(result);
                cb();
            });
  
          });
        }
  
        grunt.verbose.writeln('Transform %s -> %s', src.blue, dest.blue);
  
        async.series([
  
          // Get src and dest stat asynchronously
          function(next) {
            async.map([src,dest], function(path, done) {
              fs.stat(path, function(err, stat) {
                done(null, stat); // Ignore error
              });
            },
            function(err, results) {
              statSrc  = results[0];
              statDest = results[1];
              if(!statSrc) {
                // src is needed
                next(new Error('File not found: ' + src));
              }
              // Ignore anything that is not directory or file
              if(!(statSrc.isFile() || statSrc.isDirectory())) {
                grunt.verbose.writeln('  Ignore:'+' Source is not a file or a directory.'.grey);
                return done(null, false);
              }
  
              // Add stat to options for user defined transform & process
              options.statSrc  = statSrc;
              options.statDest = statDest;
  
              next();
            });
          },
  
          // Ignore file processing if the destination allready
          // exist with an equal or posterior mtime
          function(next) {
            if(!statDest || !lazy || readOnly) {
              return next();
            }
            if(statDest.mtime.getTime() >= statSrc.mtime.getTime()) {
              tally.useCached++;
              grunt.verbose.writeln('  Ignore %s:'+' Destination file is allready transformed (See options.lazy).'.grey, src.blue);
              // Leave proceed
              return done(null, false);
            } else {
              next();
            }
          },
  
          // Conditionnaly remove destination if
          // src / dest differ in type
          function(next) {
            if(
              readOnly  ||
              !statDest ||
              (statSrc.isFile() && statDest.isFile()) ||
              (statSrc.isDirectory() && statDest.isDirectory())
            ) {
              return next();
            } else {
              grunt.verbose.writeln('  Remove destination %s:'+' Source and destination are not of the same type.'.grey, dest.blue);
              // else remove destination first:
              dirExists[dest] = false;
              rimraf(dest, next);
            }
          },
  
          // Make directory
          function(next) {
            var dir = statSrc.isDirectory() ? dest : dirname(dest);
  
  
            if(readOnly) {
              if(statSrc.isDirectory()) {
                return done(null, true);
              } else {
                return next();
              }
            }
  
            // Make directory and leave proceed
            if(statSrc.isDirectory()) {
              // Juste make directory and leave proceed:
              if(statDest || dirExists[dir]) {
                return done(null, false);
              }
              grunt.verbose.writeln('  Make directory %s', dir.blue);
              mkdirp(dir, function(err) {
                if(err) {
                  return next(err);
                }
                tally.dirs++;
                dirExists[dir] = true;
                return done(null, true);
              });
  
            // Make file's parent directory and go next
            } else {
              if(dirExists[dir]) {
                return next();
              }
              grunt.verbose.writeln('  Make directory %s', dir.blue);
              mkdirp(dir, function(err) {
                if(err) {
                  return next(err);
                }
                tally.dirs++;
                dirExists[dir] = true;
                return next();
              });
            }
          },
  
  
          // Run copy
          function(next) {
            var wait = 0;
            function _exec() {
              var trans       = [];
              var transStream = through2();
              var mainStream, bodyStream, writeStream, temp;
  
              if(writtingFiles[dest]) {
                // Try leater
                grunt.verbose.writeln('  ... wait for %s before writing (%s)', dest, wait++);
                return setImmediate(_exec);
              }
  
              writtingFiles[dest] = true;
  
              // Create the transformation pipeline
              tally.files++;
  
              // If the file is empty, just create an empty file and go next
              if(!statSrc.size) {
                fs.writeFile(dest, '', next);
                return;
              }
  
              // Each transform builder is called and combined in
              // one readable/writable stream using stream-combiner
              if(transforms && transforms.length) {
                trans = _(transforms)
                // Call each tranform stream builder
                .map(function(f) {
                  return f(src, options.realDest, _.extend({}, options), addFiles);
                })
                // Keep only stream (ignore anything without a pipe() method)
                .filter(function(s) {
                  return s && s.pipe;
                });
                transStream = bun(trans);
              }
  
              mainStream    = new StreamStream();
              bodyStream    = fs.createReadStream(src);
  
              if(readOnly) {
                writeStream = devnull();
              } else {
                if(src === dest) {
                  writeStream = through2();
                } else {
                  writeStream = fs.createWriteStream(dest, {mode: mode});
                }
              }
  
              grunt.verbose.writeln(
                '  read(%s) -> transform(count:%s) -> write(%s)',
                src.blue,
                trans.length.toString().blue,
                dest.blue
              );
  
              // pipe all together
              if(banner){
                mainStream.write(strToStream(banner));
              }
  
              mainStream.write(bun([bodyStream, transStream]));
  
              if(footer){
                mainStream.write(strToStream(footer));
              }
  
              mainStream.end();
  
              // Write out
              mainStream.pipe(writeStream);
  
              mainStream.on('error', function(error) {
                grunt.fail.fatal(error);
              });
  
              writeStream.once('finish', function() {
                writtingFiles[dest] = false;
                if(readOnly) {
                  return next();
                }
                if(src === dest) {
                  // Copy temp file to destination.
                  writeStream
                  .pipe(fs.createWriteStream(dest, {mode: mode}))
                  .once('finish', next);
                } else {
                  next();
                }
              });
            }
            _exec();
          }
        ], function(err, result) {
          if(err) {
            grunt.log.error(err);
            grunt.log.writeln("Consider using a 'clean' task before running this task again");
            done(err);
          } else {
            // Finished with changes
            done(null, true);
          }
        });
      }