text: // TODO Add in pre and post skipping options
  module.exports = {
    /**
     *  Declares a new hook to which you can add pres and posts
     *  @param {String} name of the function
     *  @param {Function} the method
     *  @param {Function} the error handler callback
     */
    hook: function (name, fn, errorCb) {
      if (arguments.length === 1 && typeof name === 'object') {
        for (var k in name) { // `name` is a hash of hookName->hookFn
          this.hook(k, name[k]);
        }
        return;
      }
  
      var proto = this.prototype || this
        , pres = proto._pres = proto._pres || {}
        , posts = proto._posts = proto._posts || {};
      pres[name] = pres[name] || [];
      posts[name] = posts[name] || [];
  
      proto[name] = function () {
        var self = this
          , hookArgs // arguments eventually passed to the hook - are mutable
          , lastArg = arguments[arguments.length-1]
          , pres = this._pres[name]
          , posts = this._posts[name]
          , _total = pres.length
          , _current = -1
          , _asyncsLeft = proto[name].numAsyncPres
          , _next = function () {
              if (arguments[0] instanceof Error) {
                return handleError(arguments[0]);
              }
              var _args = Array.prototype.slice.call(arguments)
                , currPre
                , preArgs;
              if (_args.length && !(arguments[0] == null && typeof lastArg === 'function'))
                hookArgs = _args;
              if (++_current < _total) {
                currPre = pres[_current]
                if (currPre.isAsync && currPre.length < 2)
                  throw new Error("Your pre must have next and done arguments -- e.g., function (next, done, ...)");
                if (currPre.length < 1)
                  throw new Error("Your pre must have a next argument -- e.g., function (next, ...)");
                preArgs = (currPre.isAsync
                            ? [once(_next), once(_asyncsDone)]
                            : [once(_next)]).concat(hookArgs);
                return currPre.apply(self, preArgs);
              } else if (!proto[name].numAsyncPres) {
                return _done.apply(self, hookArgs);
              }
            }
          , _done = function () {
              var args_ = Array.prototype.slice.call(arguments)
                , ret, total_, current_, next_, done_, postArgs;
  
              if (_current === _total) {
                
                next_ = function () {
                  if (arguments[0] instanceof Error) {
                    return handleError(arguments[0]);
                  }
                  var args_ = Array.prototype.slice.call(arguments, 1)
                    , currPost
                    , postArgs;
                  if (args_.length) hookArgs = args_;
                  if (++current_ < total_) {
                    currPost = posts[current_]
                    if (currPost.length < 1)
                      throw new Error("Your post must have a next argument -- e.g., function (next, ...)");
                    postArgs = [once(next_)].concat(hookArgs);
                    return currPost.apply(self, postArgs);
                  } else if (typeof lastArg === 'function'){
                    // All post handlers are done, call original callback function
                    return lastArg.apply(self, arguments);
                  }
                };
  
                // We are assuming that if the last argument provided to the wrapped function is a function, it was expecting
                // a callback.  We trap that callback and wait to call it until all post handlers have finished.
                if(typeof lastArg === 'function'){
                  args_[args_.length - 1] = once(next_);
                }
  
                total_ = posts.length;
                current_ = -1;
                ret = fn.apply(self, args_); // Execute wrapped function, post handlers come afterward
  
                if (total_ && typeof lastArg !== 'function') return next_();  // no callback provided, execute next_() manually
                return ret;
              }
            };
        if (_asyncsLeft) {
          function _asyncsDone (err) {
            if (err && err instanceof Error) {
              return handleError(err);
            }
            --_asyncsLeft || _done.apply(self, hookArgs);
          }
        }
        function handleError (err) {
          if ('function' == typeof lastArg)
            return lastArg(err);
          if (errorCb) return errorCb.call(self, err);
          throw err;
        }
        return _next.apply(this, arguments);
      };
      
      proto[name].numAsyncPres = 0;
  
      return this;
    },
  
    pre: function (name, isAsync, fn, errorCb) {
      if ('boolean' !== typeof arguments[1]) {
        errorCb = fn;
        fn = isAsync;
        isAsync = false;
      }
      var proto = this.prototype || this
        , pres = proto._pres = proto._pres || {};
  
      this._lazySetupHooks(proto, name, errorCb);
  
      if (fn.isAsync = isAsync) {
        proto[name].numAsyncPres++;
      }
  
      (pres[name] = pres[name] || []).push(fn);
      return this;
    },
    post: function (name, isAsync, fn) {
      if (arguments.length === 2) {
        fn = isAsync;
        isAsync = false;
      }
      var proto = this.prototype || this
        , posts = proto._posts = proto._posts || {};
      
      this._lazySetupHooks(proto, name);
      (posts[name] = posts[name] || []).push(fn);
      return this;
    },
    removePre: function (name, fnToRemove) {
      var proto = this.prototype || this
        , pres = proto._pres || (proto._pres || {});
      if (!pres[name]) return this;
      if (arguments.length === 1) {
        // Remove all pre callbacks for hook `name`
        pres[name].length = 0;
      } else {
        pres[name] = pres[name].filter( function (currFn) {
          return currFn !== fnToRemove;
        });
      }
      return this;
    },
    _lazySetupHooks: function (proto, methodName, errorCb) {
      if ('undefined' === typeof proto[methodName].numAsyncPres) {
        this.hook(methodName, proto[methodName], errorCb);
      }
    }
  };
  
  function once (fn, scope) {
    return function fnWrapper () {
      if (fnWrapper.hookCalled) return;
      fnWrapper.hookCalled = true;
      return fn.apply(scope, arguments);
    };
  }
  