mirror of
				https://github.com/optim-enterprises-bv/control-pane.git
				synced 2025-11-03 19:37:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			427 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			427 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var isarray = require('isarray')
 | 
						|
 | 
						|
/**
 | 
						|
 * Expose `pathToRegexp`.
 | 
						|
 */
 | 
						|
module.exports = pathToRegexp
 | 
						|
module.exports.parse = parse
 | 
						|
module.exports.compile = compile
 | 
						|
module.exports.tokensToFunction = tokensToFunction
 | 
						|
module.exports.tokensToRegExp = tokensToRegExp
 | 
						|
 | 
						|
/**
 | 
						|
 * 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, "?", undefined]
 | 
						|
  // "/route(\\d+)"  => [undefined, undefined, undefined, "\d+", undefined, undefined]
 | 
						|
  // "/*"            => ["/", undefined, undefined, undefined, undefined, "*"]
 | 
						|
  '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'
 | 
						|
].join('|'), 'g')
 | 
						|
 | 
						|
/**
 | 
						|
 * Parse a string for the raw tokens.
 | 
						|
 *
 | 
						|
 * @param  {string}  str
 | 
						|
 * @param  {Object=} options
 | 
						|
 * @return {!Array}
 | 
						|
 */
 | 
						|
function parse (str, options) {
 | 
						|
  var tokens = []
 | 
						|
  var key = 0
 | 
						|
  var index = 0
 | 
						|
  var path = ''
 | 
						|
  var defaultDelimiter = options && options.delimiter || '/'
 | 
						|
  var res
 | 
						|
 | 
						|
  while ((res = PATH_REGEXP.exec(str)) != null) {
 | 
						|
    var m = res[0]
 | 
						|
    var escaped = res[1]
 | 
						|
    var offset = res.index
 | 
						|
    path += str.slice(index, offset)
 | 
						|
    index = offset + m.length
 | 
						|
 | 
						|
    // Ignore already escaped sequences.
 | 
						|
    if (escaped) {
 | 
						|
      path += escaped[1]
 | 
						|
      continue
 | 
						|
    }
 | 
						|
 | 
						|
    var next = str[index]
 | 
						|
    var prefix = res[2]
 | 
						|
    var name = res[3]
 | 
						|
    var capture = res[4]
 | 
						|
    var group = res[5]
 | 
						|
    var modifier = res[6]
 | 
						|
    var asterisk = res[7]
 | 
						|
 | 
						|
    // Push the current path onto the tokens.
 | 
						|
    if (path) {
 | 
						|
      tokens.push(path)
 | 
						|
      path = ''
 | 
						|
    }
 | 
						|
 | 
						|
    var partial = prefix != null && next != null && next !== prefix
 | 
						|
    var repeat = modifier === '+' || modifier === '*'
 | 
						|
    var optional = modifier === '?' || modifier === '*'
 | 
						|
    var delimiter = res[2] || defaultDelimiter
 | 
						|
    var pattern = capture || group
 | 
						|
 | 
						|
    tokens.push({
 | 
						|
      name: name || key++,
 | 
						|
      prefix: prefix || '',
 | 
						|
      delimiter: delimiter,
 | 
						|
      optional: optional,
 | 
						|
      repeat: repeat,
 | 
						|
      partial: partial,
 | 
						|
      asterisk: !!asterisk,
 | 
						|
      pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  // Match any characters still remaining.
 | 
						|
  if (index < str.length) {
 | 
						|
    path += str.substr(index)
 | 
						|
  }
 | 
						|
 | 
						|
  // If the path exists, push it onto the end.
 | 
						|
  if (path) {
 | 
						|
    tokens.push(path)
 | 
						|
  }
 | 
						|
 | 
						|
  return tokens
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Compile a string to a template function for the path.
 | 
						|
 *
 | 
						|
 * @param  {string}             str
 | 
						|
 * @param  {Object=}            options
 | 
						|
 * @return {!function(Object=, Object=)}
 | 
						|
 */
 | 
						|
function compile (str, options) {
 | 
						|
  return tokensToFunction(parse(str, options), options)
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Prettier encoding of URI path segments.
 | 
						|
 *
 | 
						|
 * @param  {string}
 | 
						|
 * @return {string}
 | 
						|
 */
 | 
						|
function encodeURIComponentPretty (str) {
 | 
						|
  return encodeURI(str).replace(/[\/?#]/g, function (c) {
 | 
						|
    return '%' + c.charCodeAt(0).toString(16).toUpperCase()
 | 
						|
  })
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Encode the asterisk parameter. Similar to `pretty`, but allows slashes.
 | 
						|
 *
 | 
						|
 * @param  {string}
 | 
						|
 * @return {string}
 | 
						|
 */
 | 
						|
function encodeAsterisk (str) {
 | 
						|
  return encodeURI(str).replace(/[?#]/g, function (c) {
 | 
						|
    return '%' + c.charCodeAt(0).toString(16).toUpperCase()
 | 
						|
  })
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Expose a method for transforming tokens into the path function.
 | 
						|
 */
 | 
						|
function tokensToFunction (tokens, options) {
 | 
						|
  // Compile all the tokens into regexps.
 | 
						|
  var matches = new Array(tokens.length)
 | 
						|
 | 
						|
  // Compile all the patterns before compilation.
 | 
						|
  for (var i = 0; i < tokens.length; i++) {
 | 
						|
    if (typeof tokens[i] === 'object') {
 | 
						|
      matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options))
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return function (obj, opts) {
 | 
						|
    var path = ''
 | 
						|
    var data = obj || {}
 | 
						|
    var options = opts || {}
 | 
						|
    var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent
 | 
						|
 | 
						|
    for (var i = 0; i < tokens.length; i++) {
 | 
						|
      var token = tokens[i]
 | 
						|
 | 
						|
      if (typeof token === 'string') {
 | 
						|
        path += token
 | 
						|
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      var value = data[token.name]
 | 
						|
      var segment
 | 
						|
 | 
						|
      if (value == null) {
 | 
						|
        if (token.optional) {
 | 
						|
          // Prepend partial segment prefixes.
 | 
						|
          if (token.partial) {
 | 
						|
            path += token.prefix
 | 
						|
          }
 | 
						|
 | 
						|
          continue
 | 
						|
        } else {
 | 
						|
          throw new TypeError('Expected "' + token.name + '" to be defined')
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (isarray(value)) {
 | 
						|
        if (!token.repeat) {
 | 
						|
          throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
 | 
						|
        }
 | 
						|
 | 
						|
        if (value.length === 0) {
 | 
						|
          if (token.optional) {
 | 
						|
            continue
 | 
						|
          } else {
 | 
						|
            throw new TypeError('Expected "' + token.name + '" to not be empty')
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        for (var j = 0; j < value.length; j++) {
 | 
						|
          segment = encode(value[j])
 | 
						|
 | 
						|
          if (!matches[i].test(segment)) {
 | 
						|
            throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
 | 
						|
          }
 | 
						|
 | 
						|
          path += (j === 0 ? token.prefix : token.delimiter) + segment
 | 
						|
        }
 | 
						|
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      segment = token.asterisk ? encodeAsterisk(value) : encode(value)
 | 
						|
 | 
						|
      if (!matches[i].test(segment)) {
 | 
						|
        throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
 | 
						|
      }
 | 
						|
 | 
						|
      path += token.prefix + segment
 | 
						|
    }
 | 
						|
 | 
						|
    return path
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Escape a regular expression string.
 | 
						|
 *
 | 
						|
 * @param  {string} str
 | 
						|
 * @return {string}
 | 
						|
 */
 | 
						|
function escapeString (str) {
 | 
						|
  return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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 && 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,
 | 
						|
        prefix: null,
 | 
						|
        delimiter: null,
 | 
						|
        optional: false,
 | 
						|
        repeat: false,
 | 
						|
        partial: false,
 | 
						|
        asterisk: false,
 | 
						|
        pattern: null
 | 
						|
      })
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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)
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create a path regexp from string input.
 | 
						|
 *
 | 
						|
 * @param  {string}  path
 | 
						|
 * @param  {!Array}  keys
 | 
						|
 * @param  {!Object} options
 | 
						|
 * @return {!RegExp}
 | 
						|
 */
 | 
						|
function stringToRegexp (path, keys, options) {
 | 
						|
  return tokensToRegExp(parse(path, options), keys, options)
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Expose a function for taking tokens and returning a RegExp.
 | 
						|
 *
 | 
						|
 * @param  {!Array}          tokens
 | 
						|
 * @param  {(Array|Object)=} keys
 | 
						|
 * @param  {Object=}         options
 | 
						|
 * @return {!RegExp}
 | 
						|
 */
 | 
						|
function tokensToRegExp (tokens, keys, options) {
 | 
						|
  if (!isarray(keys)) {
 | 
						|
    options = /** @type {!Object} */ (keys || options)
 | 
						|
    keys = []
 | 
						|
  }
 | 
						|
 | 
						|
  options = options || {}
 | 
						|
 | 
						|
  var strict = options.strict
 | 
						|
  var end = options.end !== false
 | 
						|
  var route = ''
 | 
						|
 | 
						|
  // Iterate over the tokens and create our regexp string.
 | 
						|
  for (var i = 0; i < tokens.length; i++) {
 | 
						|
    var token = tokens[i]
 | 
						|
 | 
						|
    if (typeof token === 'string') {
 | 
						|
      route += escapeString(token)
 | 
						|
    } else {
 | 
						|
      var prefix = escapeString(token.prefix)
 | 
						|
      var capture = '(?:' + token.pattern + ')'
 | 
						|
 | 
						|
      keys.push(token)
 | 
						|
 | 
						|
      if (token.repeat) {
 | 
						|
        capture += '(?:' + prefix + capture + ')*'
 | 
						|
      }
 | 
						|
 | 
						|
      if (token.optional) {
 | 
						|
        if (!token.partial) {
 | 
						|
          capture = '(?:' + prefix + '(' + capture + '))?'
 | 
						|
        } else {
 | 
						|
          capture = prefix + '(' + capture + ')?'
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        capture = prefix + '(' + capture + ')'
 | 
						|
      }
 | 
						|
 | 
						|
      route += capture
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  var delimiter = escapeString(options.delimiter || '/')
 | 
						|
  var endsWithDelimiter = route.slice(-delimiter.length) === delimiter
 | 
						|
 | 
						|
  // 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 = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'
 | 
						|
  }
 | 
						|
 | 
						|
  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 && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'
 | 
						|
  }
 | 
						|
 | 
						|
  return attachKeys(new RegExp('^' + route, flags(options)), keys)
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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|Object)=}       keys
 | 
						|
 * @param  {Object=}               options
 | 
						|
 * @return {!RegExp}
 | 
						|
 */
 | 
						|
function pathToRegexp (path, keys, options) {
 | 
						|
  if (!isarray(keys)) {
 | 
						|
    options = /** @type {!Object} */ (keys || options)
 | 
						|
    keys = []
 | 
						|
  }
 | 
						|
 | 
						|
  options = options || {}
 | 
						|
 | 
						|
  if (path instanceof RegExp) {
 | 
						|
    return regexpToRegexp(path, /** @type {!Array} */ (keys))
 | 
						|
  }
 | 
						|
 | 
						|
  if (isarray(path)) {
 | 
						|
    return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
 | 
						|
  }
 | 
						|
 | 
						|
  return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
 | 
						|
}
 |