459 lines
13 KiB
JavaScript
459 lines
13 KiB
JavaScript
"use strict";
|
|
|
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
Object.defineProperty(exports, '__esModule', {
|
|
value: true
|
|
});
|
|
exports.pathToRegexp = exports.tokensToRegexp = exports.regexpToFunction = exports.match = exports.tokensToFunction = exports.compile = exports.parse = void 0;
|
|
/**
|
|
* Tokenize input string.
|
|
*/
|
|
function lexer(str) {
|
|
var tokens = [];
|
|
var i = 0;
|
|
while (i < str.length) {
|
|
var char = str[i];
|
|
if (char === '*' || char === '+' || char === '?') {
|
|
tokens.push({
|
|
type: 'MODIFIER',
|
|
index: i,
|
|
value: str[i++]
|
|
});
|
|
continue;
|
|
}
|
|
if (char === '\\') {
|
|
tokens.push({
|
|
type: 'ESCAPED_CHAR',
|
|
index: i++,
|
|
value: str[i++]
|
|
});
|
|
continue;
|
|
}
|
|
if (char === '{') {
|
|
tokens.push({
|
|
type: 'OPEN',
|
|
index: i,
|
|
value: str[i++]
|
|
});
|
|
continue;
|
|
}
|
|
if (char === '}') {
|
|
tokens.push({
|
|
type: 'CLOSE',
|
|
index: i,
|
|
value: str[i++]
|
|
});
|
|
continue;
|
|
}
|
|
if (char === ':') {
|
|
var name = '';
|
|
var j = i + 1;
|
|
while (j < str.length) {
|
|
var code = str.charCodeAt(j);
|
|
if (
|
|
// `0-9`
|
|
code >= 48 && code <= 57 ||
|
|
// `A-Z`
|
|
code >= 65 && code <= 90 ||
|
|
// `a-z`
|
|
code >= 97 && code <= 122 ||
|
|
// `_`
|
|
code === 95) {
|
|
name += str[j++];
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (!name) throw new TypeError('Missing parameter name at ' + i);
|
|
tokens.push({
|
|
type: 'NAME',
|
|
index: i,
|
|
value: name
|
|
});
|
|
i = j;
|
|
continue;
|
|
}
|
|
if (char === '(') {
|
|
var count = 1;
|
|
var pattern = '';
|
|
var j = i + 1;
|
|
if (str[j] === '?') {
|
|
throw new TypeError('Pattern cannot start with "?" at ' + j);
|
|
}
|
|
while (j < str.length) {
|
|
if (str[j] === '\\') {
|
|
pattern += str[j++] + str[j++];
|
|
continue;
|
|
}
|
|
if (str[j] === ')') {
|
|
count--;
|
|
if (count === 0) {
|
|
j++;
|
|
break;
|
|
}
|
|
} else if (str[j] === '(') {
|
|
count++;
|
|
if (str[j + 1] !== '?') {
|
|
throw new TypeError('Capturing groups are not allowed at ' + j);
|
|
}
|
|
}
|
|
pattern += str[j++];
|
|
}
|
|
if (count) throw new TypeError('Unbalanced pattern at ' + i);
|
|
if (!pattern) throw new TypeError('Missing pattern at ' + i);
|
|
tokens.push({
|
|
type: 'PATTERN',
|
|
index: i,
|
|
value: pattern
|
|
});
|
|
i = j;
|
|
continue;
|
|
}
|
|
tokens.push({
|
|
type: 'CHAR',
|
|
index: i,
|
|
value: str[i++]
|
|
});
|
|
}
|
|
tokens.push({
|
|
type: 'END',
|
|
index: i,
|
|
value: ''
|
|
});
|
|
return tokens;
|
|
}
|
|
/**
|
|
* Parse a string for the raw tokens.
|
|
*/
|
|
function parse(str, options) {
|
|
if (options === void 0) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
options = {};
|
|
}
|
|
var tokens = lexer(str);
|
|
var _a = options.prefixes,
|
|
prefixes = _a === void 0 ? './' : _a;
|
|
var defaultPattern = '[^' + escapeString(options.delimiter || '/#?') + ']+?';
|
|
var result = [];
|
|
var key = 0;
|
|
var i = 0;
|
|
var path = '';
|
|
var tryConsume = function tryConsume(type) {
|
|
if (i < tokens.length && tokens[i].type === type) return tokens[i++].value;
|
|
};
|
|
var mustConsume = function mustConsume(type) {
|
|
var value = tryConsume(type);
|
|
if (value !== undefined) return value;
|
|
var _a = tokens[i],
|
|
nextType = _a.type,
|
|
index = _a.index;
|
|
throw new TypeError('Unexpected ' + nextType + ' at ' + index + ', expected ' + type);
|
|
};
|
|
var consumeText = function consumeText() {
|
|
var result = '';
|
|
var value;
|
|
// tslint:disable-next-line
|
|
while (value = tryConsume('CHAR') || tryConsume('ESCAPED_CHAR')) {
|
|
result += value;
|
|
}
|
|
return result;
|
|
};
|
|
while (i < tokens.length) {
|
|
var char = tryConsume('CHAR');
|
|
var name = tryConsume('NAME');
|
|
var pattern = tryConsume('PATTERN');
|
|
if (name || pattern) {
|
|
var prefix = char || '';
|
|
if (prefixes.indexOf(prefix) === -1) {
|
|
path += prefix;
|
|
prefix = '';
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = '';
|
|
}
|
|
result.push({
|
|
name: name || key++,
|
|
prefix: prefix,
|
|
suffix: '',
|
|
pattern: pattern || defaultPattern,
|
|
modifier: tryConsume('MODIFIER') || ''
|
|
});
|
|
continue;
|
|
}
|
|
var value = char || tryConsume('ESCAPED_CHAR');
|
|
if (value) {
|
|
path += value;
|
|
continue;
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = '';
|
|
}
|
|
var open = tryConsume('OPEN');
|
|
if (open) {
|
|
var prefix = consumeText();
|
|
var name_1 = tryConsume('NAME') || '';
|
|
var pattern_1 = tryConsume('PATTERN') || '';
|
|
var suffix = consumeText();
|
|
mustConsume('CLOSE');
|
|
result.push({
|
|
name: name_1 || (pattern_1 ? key++ : ''),
|
|
pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1,
|
|
prefix: prefix,
|
|
suffix: suffix,
|
|
modifier: tryConsume('MODIFIER') || ''
|
|
});
|
|
continue;
|
|
}
|
|
mustConsume('END');
|
|
}
|
|
return result;
|
|
}
|
|
exports.parse = parse;
|
|
/**
|
|
* Compile a string to a template function for the path.
|
|
*/
|
|
function compile(str, options) {
|
|
return tokensToFunction(parse(str, options), options);
|
|
}
|
|
exports.compile = compile;
|
|
/**
|
|
* Expose a method for transforming tokens into the path function.
|
|
*/
|
|
function tokensToFunction(tokens, options) {
|
|
if (options === void 0) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
options = {};
|
|
}
|
|
var reFlags = flags(options);
|
|
var _a = options.encode,
|
|
encode = _a === void 0 ? function (x) {
|
|
return x;
|
|
} : _a,
|
|
_b = options.validate,
|
|
validate = _b === void 0 ? true : _b;
|
|
// Compile all the tokens into regexps.
|
|
var matches = tokens.map(function (token) {
|
|
if (_typeof(token) === 'object') {
|
|
return new RegExp('^(?:' + token.pattern + ')$', reFlags);
|
|
}
|
|
});
|
|
return function (data) {
|
|
var path = '';
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
var token = tokens[i];
|
|
if (typeof token === 'string') {
|
|
path += token;
|
|
continue;
|
|
}
|
|
var value = data ? data[token.name] : undefined;
|
|
var optional = token.modifier === '?' || token.modifier === '*';
|
|
var repeat = token.modifier === '*' || token.modifier === '+';
|
|
if (Array.isArray(value)) {
|
|
if (!repeat) {
|
|
throw new TypeError('Expected "' + token.name + '" to not repeat, but got an array');
|
|
}
|
|
if (value.length === 0) {
|
|
if (optional) continue;
|
|
throw new TypeError('Expected "' + token.name + '" to not be empty');
|
|
}
|
|
for (var j = 0; j < value.length; j++) {
|
|
var segment = encode(value[j], token);
|
|
if (validate && !matches[i].test(segment)) {
|
|
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but got "' + segment + '"');
|
|
}
|
|
path += token.prefix + segment + token.suffix;
|
|
}
|
|
continue;
|
|
}
|
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
var segment = encode(String(value), token);
|
|
if (validate && !matches[i].test(segment)) {
|
|
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but got "' + segment + '"');
|
|
}
|
|
path += token.prefix + segment + token.suffix;
|
|
continue;
|
|
}
|
|
if (optional) continue;
|
|
var typeOfMessage = repeat ? 'an array' : 'a string';
|
|
throw new TypeError('Expected "' + token.name + '" to be ' + typeOfMessage);
|
|
}
|
|
return path;
|
|
};
|
|
}
|
|
exports.tokensToFunction = tokensToFunction;
|
|
/**
|
|
* Create path match function from `path-to-regexp` spec.
|
|
*/
|
|
function match(str, options) {
|
|
var keys = [];
|
|
var re = pathToRegexp(str, keys, options);
|
|
return regexpToFunction(re, keys, options);
|
|
}
|
|
exports.match = match;
|
|
/**
|
|
* Create a path match function from `path-to-regexp` output.
|
|
*/
|
|
function regexpToFunction(re, keys, options) {
|
|
if (options === void 0) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
options = {};
|
|
}
|
|
var _a = options.decode,
|
|
decode = _a === void 0 ? function (x) {
|
|
return x;
|
|
} : _a;
|
|
return function (pathname) {
|
|
var m = re.exec(pathname);
|
|
if (!m) return false;
|
|
var path = m[0],
|
|
index = m.index;
|
|
var params = Object.create(null);
|
|
var _loop_1 = function _loop_1(i) {
|
|
// tslint:disable-next-line
|
|
if (m[i] === undefined) return 'continue';
|
|
var key = keys[i - 1];
|
|
if (key.modifier === '*' || key.modifier === '+') {
|
|
params[key.name] = m[i].split(key.prefix + key.suffix).map(function (value) {
|
|
return decode(value, key);
|
|
});
|
|
} else {
|
|
params[key.name] = decode(m[i], key);
|
|
}
|
|
};
|
|
for (var i = 1; i < m.length; i++) {
|
|
_loop_1(i);
|
|
}
|
|
return {
|
|
path: path,
|
|
index: index,
|
|
params: params
|
|
};
|
|
};
|
|
}
|
|
exports.regexpToFunction = regexpToFunction;
|
|
/**
|
|
* Escape a regular expression string.
|
|
*/
|
|
function escapeString(str) {
|
|
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1');
|
|
}
|
|
/**
|
|
* Get the flags for a regexp from the options.
|
|
*/
|
|
function flags(options) {
|
|
return options && options.sensitive ? '' : 'i';
|
|
}
|
|
/**
|
|
* Pull out keys from a regexp.
|
|
*/
|
|
function regexpToRegexp(path, keys) {
|
|
if (!keys) return path;
|
|
// 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: '',
|
|
suffix: '',
|
|
modifier: '',
|
|
pattern: ''
|
|
});
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
/**
|
|
* Transform an array into a regexp.
|
|
*/
|
|
function arrayToRegexp(paths, keys, options) {
|
|
var parts = paths.map(function (path) {
|
|
return pathToRegexp(path, keys, options).source;
|
|
});
|
|
return new RegExp('(?:' + parts.join('|') + ')', flags(options));
|
|
}
|
|
/**
|
|
* Create a path regexp from string input.
|
|
*/
|
|
function stringToRegexp(path, keys, options) {
|
|
return tokensToRegexp(parse(path, options), keys, options);
|
|
}
|
|
/**
|
|
* Expose a function for taking tokens and returning a RegExp.
|
|
*/
|
|
function tokensToRegexp(tokens, keys, options) {
|
|
if (options === void 0) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
options = {};
|
|
}
|
|
var _a = options.strict,
|
|
strict = _a === void 0 ? false : _a,
|
|
_b = options.start,
|
|
start = _b === void 0 ? true : _b,
|
|
_c = options.end,
|
|
end = _c === void 0 ? true : _c,
|
|
_d = options.encode,
|
|
encode = _d === void 0 ? function (x) {
|
|
return x;
|
|
} : _d;
|
|
var endsWith = '[' + escapeString(options.endsWith || '') + ']|$';
|
|
var delimiter = '[' + escapeString(options.delimiter || '/#?') + ']';
|
|
var route = start ? '^' : '';
|
|
// Iterate over the tokens and create our regexp string.
|
|
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
|
|
var token = tokens_1[_i];
|
|
if (typeof token === 'string') {
|
|
route += escapeString(encode(token));
|
|
} else {
|
|
var prefix = escapeString(encode(token.prefix));
|
|
var suffix = escapeString(encode(token.suffix));
|
|
if (token.pattern) {
|
|
if (keys) keys.push(token);
|
|
if (prefix || suffix) {
|
|
if (token.modifier === '+' || token.modifier === '*') {
|
|
var mod = token.modifier === '*' ? '?' : '';
|
|
route += '(?:' + prefix + '((?:' + token.pattern + ')(?:' + suffix + prefix + '(?:' + token.pattern + '))*)' + suffix + ')' + mod;
|
|
} else {
|
|
route += '(?:' + prefix + '(' + token.pattern + ')' + suffix + ')' + token.modifier;
|
|
}
|
|
} else {
|
|
route += '(' + token.pattern + ')' + token.modifier;
|
|
}
|
|
} else {
|
|
route += '(?:' + prefix + suffix + ')' + token.modifier;
|
|
}
|
|
}
|
|
}
|
|
if (end) {
|
|
if (!strict) route += delimiter + '?';
|
|
route += !options.endsWith ? '$' : '(?=' + endsWith + ')';
|
|
} else {
|
|
var endToken = tokens[tokens.length - 1];
|
|
var isEndDelimited = typeof endToken === 'string' ? delimiter.indexOf(endToken[endToken.length - 1]) > -1 :
|
|
// tslint:disable-next-line
|
|
endToken === undefined;
|
|
if (!strict) {
|
|
route += '(?:' + delimiter + '(?=' + endsWith + '))?';
|
|
}
|
|
if (!isEndDelimited) {
|
|
route += '(?=' + delimiter + '|' + endsWith + ')';
|
|
}
|
|
}
|
|
return new RegExp(route, flags(options));
|
|
}
|
|
exports.tokensToRegexp = tokensToRegexp;
|
|
/**
|
|
* 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 }]`.
|
|
*/
|
|
function pathToRegexp(path, keys, options) {
|
|
if (path instanceof RegExp) return regexpToRegexp(path, keys);
|
|
if (Array.isArray(path)) return arrayToRegexp(path, keys, options);
|
|
return stringToRegexp(path, keys, options);
|
|
}
|
|
exports.pathToRegexp = pathToRegexp; |