123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.TokenData = void 0;
- exports.parse = parse;
- exports.compile = compile;
- exports.match = match;
- exports.pathToRegexp = pathToRegexp;
- exports.stringify = stringify;
- const DEFAULT_DELIMITER = "/";
- const NOOP_VALUE = (value) => value;
- const ID_START = /^[$_\p{ID_Start}]$/u;
- const ID_CONTINUE = /^[$\u200c\u200d\p{ID_Continue}]$/u;
- const DEBUG_URL = "https://git.new/pathToRegexpError";
- const SIMPLE_TOKENS = {
- // Groups.
- "{": "{",
- "}": "}",
- // Reserved.
- "(": "(",
- ")": ")",
- "[": "[",
- "]": "]",
- "+": "+",
- "?": "?",
- "!": "!",
- };
- /**
- * Escape text for stringify to path.
- */
- function escapeText(str) {
- return str.replace(/[{}()\[\]+?!:*]/g, "\\$&");
- }
- /**
- * Escape a regular expression string.
- */
- function escape(str) {
- return str.replace(/[.+*?^${}()[\]|/\\]/g, "\\$&");
- }
- /**
- * Tokenize input string.
- */
- function* lexer(str) {
- const chars = [...str];
- let i = 0;
- function name() {
- let value = "";
- if (ID_START.test(chars[++i])) {
- value += chars[i];
- while (ID_CONTINUE.test(chars[++i])) {
- value += chars[i];
- }
- }
- else if (chars[i] === '"') {
- let pos = i;
- while (i < chars.length) {
- if (chars[++i] === '"') {
- i++;
- pos = 0;
- break;
- }
- if (chars[i] === "\\") {
- value += chars[++i];
- }
- else {
- value += chars[i];
- }
- }
- if (pos) {
- throw new TypeError(`Unterminated quote at ${pos}: ${DEBUG_URL}`);
- }
- }
- if (!value) {
- throw new TypeError(`Missing parameter name at ${i}: ${DEBUG_URL}`);
- }
- return value;
- }
- while (i < chars.length) {
- const value = chars[i];
- const type = SIMPLE_TOKENS[value];
- if (type) {
- yield { type, index: i++, value };
- }
- else if (value === "\\") {
- yield { type: "ESCAPED", index: i++, value: chars[i++] };
- }
- else if (value === ":") {
- const value = name();
- yield { type: "PARAM", index: i, value };
- }
- else if (value === "*") {
- const value = name();
- yield { type: "WILDCARD", index: i, value };
- }
- else {
- yield { type: "CHAR", index: i, value: chars[i++] };
- }
- }
- return { type: "END", index: i, value: "" };
- }
- class Iter {
- constructor(tokens) {
- this.tokens = tokens;
- }
- peek() {
- if (!this._peek) {
- const next = this.tokens.next();
- this._peek = next.value;
- }
- return this._peek;
- }
- tryConsume(type) {
- const token = this.peek();
- if (token.type !== type)
- return;
- this._peek = undefined; // Reset after consumed.
- return token.value;
- }
- consume(type) {
- const value = this.tryConsume(type);
- if (value !== undefined)
- return value;
- const { type: nextType, index } = this.peek();
- throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}: ${DEBUG_URL}`);
- }
- text() {
- let result = "";
- let value;
- while ((value = this.tryConsume("CHAR") || this.tryConsume("ESCAPED"))) {
- result += value;
- }
- return result;
- }
- }
- /**
- * Tokenized path instance.
- */
- class TokenData {
- constructor(tokens) {
- this.tokens = tokens;
- }
- }
- exports.TokenData = TokenData;
- /**
- * Parse a string for the raw tokens.
- */
- function parse(str, options = {}) {
- const { encodePath = NOOP_VALUE } = options;
- const it = new Iter(lexer(str));
- function consume(endType) {
- const tokens = [];
- while (true) {
- const path = it.text();
- if (path)
- tokens.push({ type: "text", value: encodePath(path) });
- const param = it.tryConsume("PARAM");
- if (param) {
- tokens.push({
- type: "param",
- name: param,
- });
- continue;
- }
- const wildcard = it.tryConsume("WILDCARD");
- if (wildcard) {
- tokens.push({
- type: "wildcard",
- name: wildcard,
- });
- continue;
- }
- const open = it.tryConsume("{");
- if (open) {
- tokens.push({
- type: "group",
- tokens: consume("}"),
- });
- continue;
- }
- it.consume(endType);
- return tokens;
- }
- }
- const tokens = consume("END");
- return new TokenData(tokens);
- }
- /**
- * Compile a string to a template function for the path.
- */
- function compile(path, options = {}) {
- const { encode = encodeURIComponent, delimiter = DEFAULT_DELIMITER } = options;
- const data = path instanceof TokenData ? path : parse(path, options);
- const fn = tokensToFunction(data.tokens, delimiter, encode);
- return function path(data = {}) {
- const [path, ...missing] = fn(data);
- if (missing.length) {
- throw new TypeError(`Missing parameters: ${missing.join(", ")}`);
- }
- return path;
- };
- }
- function tokensToFunction(tokens, delimiter, encode) {
- const encoders = tokens.map((token) => tokenToFunction(token, delimiter, encode));
- return (data) => {
- const result = [""];
- for (const encoder of encoders) {
- const [value, ...extras] = encoder(data);
- result[0] += value;
- result.push(...extras);
- }
- return result;
- };
- }
- /**
- * Convert a single token into a path building function.
- */
- function tokenToFunction(token, delimiter, encode) {
- if (token.type === "text")
- return () => [token.value];
- if (token.type === "group") {
- const fn = tokensToFunction(token.tokens, delimiter, encode);
- return (data) => {
- const [value, ...missing] = fn(data);
- if (!missing.length)
- return [value];
- return [""];
- };
- }
- const encodeValue = encode || NOOP_VALUE;
- if (token.type === "wildcard" && encode !== false) {
- return (data) => {
- const value = data[token.name];
- if (value == null)
- return ["", token.name];
- if (!Array.isArray(value) || value.length === 0) {
- throw new TypeError(`Expected "${token.name}" to be a non-empty array`);
- }
- return [
- value
- .map((value, index) => {
- if (typeof value !== "string") {
- throw new TypeError(`Expected "${token.name}/${index}" to be a string`);
- }
- return encodeValue(value);
- })
- .join(delimiter),
- ];
- };
- }
- return (data) => {
- const value = data[token.name];
- if (value == null)
- return ["", token.name];
- if (typeof value !== "string") {
- throw new TypeError(`Expected "${token.name}" to be a string`);
- }
- return [encodeValue(value)];
- };
- }
- /**
- * Transform a path into a match function.
- */
- function match(path, options = {}) {
- const { decode = decodeURIComponent, delimiter = DEFAULT_DELIMITER } = options;
- const { regexp, keys } = pathToRegexp(path, options);
- const decoders = keys.map((key) => {
- if (decode === false)
- return NOOP_VALUE;
- if (key.type === "param")
- return decode;
- return (value) => value.split(delimiter).map(decode);
- });
- return function match(input) {
- const m = regexp.exec(input);
- if (!m)
- return false;
- const path = m[0];
- const params = Object.create(null);
- for (let i = 1; i < m.length; i++) {
- if (m[i] === undefined)
- continue;
- const key = keys[i - 1];
- const decoder = decoders[i - 1];
- params[key.name] = decoder(m[i]);
- }
- return { path, params };
- };
- }
- function pathToRegexp(path, options = {}) {
- const { delimiter = DEFAULT_DELIMITER, end = true, sensitive = false, trailing = true, } = options;
- const keys = [];
- const sources = [];
- const flags = sensitive ? "" : "i";
- const paths = Array.isArray(path) ? path : [path];
- const items = paths.map((path) => path instanceof TokenData ? path : parse(path, options));
- for (const { tokens } of items) {
- for (const seq of flatten(tokens, 0, [])) {
- const regexp = sequenceToRegExp(seq, delimiter, keys);
- sources.push(regexp);
- }
- }
- let pattern = `^(?:${sources.join("|")})`;
- if (trailing)
- pattern += `(?:${escape(delimiter)}$)?`;
- pattern += end ? "$" : `(?=${escape(delimiter)}|$)`;
- const regexp = new RegExp(pattern, flags);
- return { regexp, keys };
- }
- /**
- * Generate a flat list of sequence tokens from the given tokens.
- */
- function* flatten(tokens, index, init) {
- if (index === tokens.length) {
- return yield init;
- }
- const token = tokens[index];
- if (token.type === "group") {
- const fork = init.slice();
- for (const seq of flatten(token.tokens, 0, fork)) {
- yield* flatten(tokens, index + 1, seq);
- }
- }
- else {
- init.push(token);
- }
- yield* flatten(tokens, index + 1, init);
- }
- /**
- * Transform a flat sequence of tokens into a regular expression.
- */
- function sequenceToRegExp(tokens, delimiter, keys) {
- let result = "";
- let backtrack = "";
- let isSafeSegmentParam = true;
- for (let i = 0; i < tokens.length; i++) {
- const token = tokens[i];
- if (token.type === "text") {
- result += escape(token.value);
- backtrack += token.value;
- isSafeSegmentParam || (isSafeSegmentParam = token.value.includes(delimiter));
- continue;
- }
- if (token.type === "param" || token.type === "wildcard") {
- if (!isSafeSegmentParam && !backtrack) {
- throw new TypeError(`Missing text after "${token.name}": ${DEBUG_URL}`);
- }
- if (token.type === "param") {
- result += `(${negate(delimiter, isSafeSegmentParam ? "" : backtrack)}+)`;
- }
- else {
- result += `([\\s\\S]+)`;
- }
- keys.push(token);
- backtrack = "";
- isSafeSegmentParam = false;
- continue;
- }
- }
- return result;
- }
- function negate(delimiter, backtrack) {
- if (backtrack.length < 2) {
- if (delimiter.length < 2)
- return `[^${escape(delimiter + backtrack)}]`;
- return `(?:(?!${escape(delimiter)})[^${escape(backtrack)}])`;
- }
- if (delimiter.length < 2) {
- return `(?:(?!${escape(backtrack)})[^${escape(delimiter)}])`;
- }
- return `(?:(?!${escape(backtrack)}|${escape(delimiter)})[\\s\\S])`;
- }
- /**
- * Stringify token data into a path string.
- */
- function stringify(data) {
- return data.tokens
- .map(function stringifyToken(token, index, tokens) {
- if (token.type === "text")
- return escapeText(token.value);
- if (token.type === "group") {
- return `{${token.tokens.map(stringifyToken).join("")}}`;
- }
- const isSafe = isNameSafe(token.name) && isNextNameSafe(tokens[index + 1]);
- const key = isSafe ? token.name : JSON.stringify(token.name);
- if (token.type === "param")
- return `:${key}`;
- if (token.type === "wildcard")
- return `*${key}`;
- throw new TypeError(`Unexpected token: ${token}`);
- })
- .join("");
- }
- function isNameSafe(name) {
- const [first, ...rest] = name;
- if (!ID_START.test(first))
- return false;
- return rest.every((char) => ID_CONTINUE.test(char));
- }
- function isNextNameSafe(token) {
- if ((token === null || token === void 0 ? void 0 : token.type) !== "text")
- return true;
- return !ID_CONTINUE.test(token.value[0]);
- }
- //# sourceMappingURL=index.js.map
|