docs: function generate(target, data, done) {
          var files = this.files,
              dest = null,
              paths = [],
              promises = [],
              folders = [];
  
          //by default, we generate a table of contents called readme.md
          var options = this.options({
              contentsEnabled: true,
              contentsFile: 'readme.md',
              contentsTitle: 'Documentation',
              templateDir: null
          });
  
          //map the list of files we'll be generating documentation for
          files.forEach(function(group) {
              dest = group.dest;
              group.src.filter(function(file) {
                  var obj = {
                      filename: path.basename(file),
                      folder: path.dirname(file),
                      path: file
                  };
  
                  if(options.pathFilter)
                      obj.folder = obj.folder.replace(options.pathFilter, '');
  
                  folders.push(obj);
              });
          });
  
          //map the set of folders, sort, and generate documentation
          folders = _(folders)
              .groupBy(function(p) { return p.folder; })
              .sortBy(function(p) { return p.length && p[0].folder; })
              .map(function(group) {
                  var file = group.length && group[0] || {};
                  return {
                      name: file.folder,
                      path: file.path.replace(file.filename, ''),
                      files: group,
                      fileData: {}
                  };
              })
              .each(function(folder) {
                  var def = Q.defer();
  
                  //queue the promise for the docs, and ensure the target exists (can safely run on an existing folder)
                  promises.push(def.promise);
                  grunt.file.mkdir(dest +'/'+ folder.name);
  
                  //generate the docs for the folder, resolving the promise when complete
                  jsdox.generateForDir(folder.path, dest +'/'+ folder.name, options.templateDir, def.resolve, function(file, fileData) {
                      folder.fileData[file] = fileData;
                  });
              });
  
          //when all docs have been generated, build the table of contents, if needed (this stuff should be moved into a template)
          Q.all(promises).then(function() {
              if(!options.contentsEnabled) return done();
  
              var buf = [],
                  w = function() { buf.push.apply(buf, arguments); };
  
              w(options.contentsTitle || 'Documentation', '===');
  
              folders.forEach(function(folder) {
                  var docs = [];
  
                  //derive the set of documents we *actually* generated docs for; that way we only add documented
                  //code to the table of contents
                  folder.files.forEach(function(file) {
                      //reset the path, pointing to the markdown file, and set the file data parsed by jsdox
                      file.path = path.join(file.folder, file.filename.replace('.js', '')).replace(/^\//, '');
                      file.data = folder.fileData[file.filename]||{};
                      if(grunt.file.exists(dest, file.path + '.md')) docs.push(file);
                  });
  
                  if(docs.length > 0) {
                      w(folder.name || 'Root', '---');
                      w('name | overview', ':-- | :--');
                      docs.forEach(function(file) {
                          w('['+ file.path +'.js]('+ file.path + '.md' + ') | '+ (file.data.overview?'_'+ file.data.overview +'_':''));
                      });
                      w('- - -');
                  }
  
                  w('\n');
              });
  
              //write out the table of contents, and complete the async generate task
              fs.writeFileSync(path.join(dest, (options.contentsFile || 'readme.md')), buf.join('\n'));
              done();
          });
      }