text: // TODO
  //  * add types by listener dynamically
  //  * unroll loops for better readability?
  //  * should emitted errors wait for `next()`?
  (function (undefined) {
    var fs = require('fs'),
      upath = require('path'),
      util = require('util'),
      Futures = require('futures'),
      events = require('events'),
      noop = function () {},
      // "FIFO" isn't easy to convert to camelCame and back reliably
      isFnodeTypes = [
        "isFile", "isDirectory",  "isBlockDevice",  "isCharacterDevice",  "isSymbolicLink", "isFIFO", "isSocket"
      ],
      fnodeTypes = [
        "file",   "directory",    "blockDevice",    "characterDevice",    "symbolicLink",   "FIFO",   "socket"
      ],
      fnodeTypesPlural = [
        "files",  "directories",  "blockDevices",   "characterDevices",   "symbolicLinks",  "FIFOs",  "sockets"
      ];
  
    function newVersion() {
      throw new Error("New Version. Please see API on github.com/coolaj86/node-walk");
    }
  
    // Create a new walk instance
    function create(path, options, cb) {
      if (cb) {
        newVersion();
      }
      
      var emitter = new events.EventEmitter(),
        fstat = (options||{}).followLinks ? fs.stat : fs.lstat;
  
  
      // Get the current number of listeners (which may change)
      // Emit events to each listener
      // Wait for all listeners to `next()` before continueing
      // (in theory this may avoid disk thrashing)
      function emitSingleEvents(path, stats, next) {
        var num = 1 + emitter.listeners(stats.type).length + emitter.listeners("node").length;
  
        function nextWhenReady() {
          num -= 1;
          if (0 === num) { next(); }
        }
  
        emitter.emit(stats.type, path, stats, nextWhenReady);
        emitter.emit("node", path, stats, nextWhenReady);
        nextWhenReady();
      }
  
  
      // Since the risk for disk thrashing among anything
      // other than files is relatively low, all types are
      // emitted at once, but all must complete before advancing
      function emitPluralEvents(path, nodes, next) {
        var num = 1;
  
        function nextWhenReady() {
          num -= 1;
          if (0 === num) { next(); }
        }
  
        fnodeTypesPlural.concat(["nodes", "errors"]).forEach(function (fnodeType) {
          if (0 === nodes[fnodeType].length) { return; }
          num += emitter.listeners(fnodeType).length;
          emitter.emit(fnodeType, path, nodes[fnodeType], nextWhenReady);
        });
        nextWhenReady();
      }
  
  
      // Determine each file node's type
      // 
      function sortFnodesByType(path, stats, fnodes, nextFile) {
        isFnodeTypes.forEach(function (isType, i) {
          if (stats[isType]()) {
            if (stats.type) { throw new Error("is_" + type + " and " + isType); }
            stats.type = fnodeTypes[i];
            fnodes[fnodeTypesPlural[i]].push(stats);
            // TODO throw to break;
          }
        });
        if (!stats.type) { throw new Error(upath.join(path, stats.name) + ' isAnUndefinedType'); }
        emitSingleEvents(path, stats, nextFile);
      }
  
  
      // Asynchronously get the stats 
      //
      function getStats(path, files, walkDirs) {
        var nodeGroups = {};
  
        fnodeTypesPlural.concat("nodes", "errors").forEach(function (fnodeTypePlural) {
          nodeGroups[fnodeTypePlural] = [];
        });
  
        function nextFile() {
          var file = files.pop(), dirs = [], fnames = [];
  
          if (undefined === file) {
            emitPluralEvents(path, nodeGroups, function () {
              nodeGroups.directories.forEach(function (dir) {
                dirs.push(dir.name);
              });
              walkDirs(dirs);
            });
            return;
          }
  
          fstat(upath.join(path, file), function (err, stats) {
            stats = stats || {};
            stats.name = file;
            nodeGroups.nodes.push(stats);
            if (err) {
              stats.error = err;
              stats.type = 'error';
              nodeGroups.errors.push(stats);
              //emitter.emit('fileError', path, stats, noop);
              return nextFile();
            }
            sortFnodesByType(path, stats, nodeGroups, nextFile);
          });
        }
        nextFile();
      }
  
      function walk(path, next) {
        fs.readdir(path, function (err, nodes) {
          if (err) { 
            emitter.emit('directoryError', path, { error: err, name: path }, noop);
            return next(); /*TODO*/ throw err;
          }
          getStats(path, nodes, function (dirs) {
            walkDirs(path, dirs, next);
          });
        });
      }
  
      function walkDirs(path, dirs, cb) {
        function nextDir() {
          var dir = dirs.pop();
          if (undefined === dir) {
            delete dirs;
            return cb();
          }
          walk(upath.join(path, dir), nextDir);
        }
        nextDir();
      }
  
      walk(upath.normalize(path), function () {
        emitter.emit('end');
      });
      emitter.walk = newVersion;
      emitter.whenever = newVersion;
      return emitter;
    }
    module.exports = create;
    module.exports.isFnodeTypes = isFnodeTypes;
    module.exports.fnodeTypes = fnodeTypes;
    module.exports.fnodeTypesPlural = fnodeTypesPlural;
  }());
  