text: var isArray = Array.isArray || function (arr) {
    return Object.prototype.toString.call(arr) == '[object Array]';
  };
  
  /**
   * Expose `pathToRegexp`.
   */
  // module.exports = pathToRegexp
  
  /**
   * The main path matching regexp utility.
   *
   * @type {RegExp}
   */
  var PATH_REGEXP = new RegExp([
    // Match escaped characters that would otherwise appear in future matches.
    // This allows the user to escape special characters that won't transform.
    '(\\\\.)',
    // Match Express-style parameters and un-named parameters with a prefix
    // and optional suffixes. Matches appear as:
    //
    // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"]
    // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
    '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?',
    // Match regexp special characters that are always escaped.
    '([.+*?=^!:${}()[\\]|\\/])'
  ].join('|'), 'g');
  
  /**
   * Escape the capturing group by escaping special characters and meaning.
   *
   * @param  {String} group
   * @return {String}
   */
  function escapeGroup (group) {
    return group.replace(/([=!:$\/()])/g, '\\$1');
  }
  
  /**
   * Attach the keys as a property of the regexp.
   *
   * @param  {RegExp} re
   * @param  {Array}  keys
   * @return {RegExp}
   */
  function attachKeys (re, keys) {
    re.keys = keys;
    return re;
  }
  
  /**
   * Get the flags for a regexp from the options.
   *
   * @param  {Object} options
   * @return {String}
   */
  function flags (options) {
    return options.sensitive ? '' : 'i';
  }
  
  /**
   * Pull out keys from a regexp.
   *
   * @param  {RegExp} path
   * @param  {Array}  keys
   * @return {RegExp}
   */
  function regexpToRegexp (path, keys) {
    // Use a negative lookahead to match only capturing groups.
    var groups = path.source.match(/\((?!\?)/g);
  
    if (groups) {
      for (var i = 0; i < groups.length; i++) {
        keys.push({
          name:      i,
          delimiter: null,
          optional:  false,
          repeat:    false
        });
      }
    }
  
    return attachKeys(path, keys);
  }
  
  /**
   * Transform an array into a regexp.
   *
   * @param  {Array}  path
   * @param  {Array}  keys
   * @param  {Object} options
   * @return {RegExp}
   */
  function arrayToRegexp (path, keys, options) {
    var parts = [];
  
    for (var i = 0; i < path.length; i++) {
      parts.push(pathToRegexp(path[i], keys, options).source);
    }
  
    var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options));
    return attachKeys(regexp, keys);
  }
  
  /**
   * Replace the specific tags with regexp strings.
   *
   * @param  {String} path
   * @param  {Array}  keys
   * @return {String}
   */
  function replacePath (path, keys) {
    var index = 0;
  
    function replace (_, escaped, prefix, key, capture, group, suffix, escape) {
      if (escaped) {
        return escaped;
      }
  
      if (escape) {
        return '\\' + escape;
      }
  
      var repeat   = suffix === '+' || suffix === '*';
      var optional = suffix === '?' || suffix === '*';
  
      keys.push({
        name:      key || index++,
        delimiter: prefix || '/',
        optional:  optional,
        repeat:    repeat
      });
  
      prefix = prefix ? ('\\' + prefix) : '';
      capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?');
  
      if (repeat) {
        capture = capture + '(?:' + prefix + capture + ')*';
      }
  
      if (optional) {
        return '(?:' + prefix + '(' + capture + '))?';
      }
  
      // Basic parameter support.
      return prefix + '(' + capture + ')';
    }
  
    return path.replace(PATH_REGEXP, replace);
  }
  
  /**
   * Normalize the given path string, returning a regular expression.
   *
   * An empty array can be passed in for the keys, which will hold the
   * placeholder key descriptions. For example, using `/user/:id`, `keys` will
   * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
   *
   * @param  {(String|RegExp|Array)} path
   * @param  {Array}                 [keys]
   * @param  {Object}                [options]
   * @return {RegExp}
   */
  function pathToRegexp (path, keys, options) {
    keys = keys || [];
  
    if (!isArray(keys)) {
      options = keys;
      keys = [];
    } else if (!options) {
      options = {};
    }
  
    if (path instanceof RegExp) {
      return regexpToRegexp(path, keys, options);
    }
  
    if (isArray(path)) {
      return arrayToRegexp(path, keys, options);
    }
  
    var strict = options.strict;
    var end = options.end !== false;
    var route = replacePath(path, keys);
    var endsWithSlash = path.charAt(path.length - 1) === '/';
  
    // In non-strict mode we allow a slash at the end of match. If the path to
    // match already ends with a slash, we remove it for consistency. The slash
    // is valid at the end of a path match, not in the middle. This is important
    // in non-ending mode, where "/test/" shouldn't match "/test//route".
    if (!strict) {
      route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?';
    }
  
    if (end) {
      route += '$';
    } else {
      // In non-ending mode, we need the capturing groups to match as much as
      // possible by using a positive lookahead to the end or next path segment.
      route += strict && endsWithSlash ? '' : '(?=\\/|$)';
    }
  
    return attachKeys(new RegExp('^' + route, flags(options)), keys);
  }
  