Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

21
node_modules/regex-recursion/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Steven Levithan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

143
node_modules/regex-recursion/README.md generated vendored Normal file
View File

@@ -0,0 +1,143 @@
# regex-recursion
[![npm version][npm-version-src]][npm-version-href]
[![bundle][bundle-src]][bundle-href]
This is an official plugin for [Regex+](https://github.com/slevithan/regex) that adds support for recursive matching up to a specified max depth *N*, where *N* can be between 2 and 100. Generated regexes are native JavaScript `RegExp` instances.
Recursive matching is added to a regex via one of the following (the recursion depth limit is provided in place of *`N`*):
- `(?R=N)` — Recursively match the entire regex at this position.
- `\g<name&R=N>` or `\g<number&R=N>` — Recursively match the contents of the group referenced by name or number at this position.
- The `\g` subroutine must be *within* the referenced group.
Multiple uses of recursion within the same pattern are allowed if they are non-overlapping. Named captures and backreferences are supported within recursion, and are independent per depth level. So e.g. `groups.name` on a match object is the value captured by group `name` at the top level of the recursion stack.
## Install and use
```sh
npm install regex regex-recursion
```
```js
import {regex} from 'regex';
import {recursion} from 'regex-recursion';
const re = regex({plugins: [recursion]})`…`;
```
<details>
<summary>Using a global name (no import)</summary>
```html
<script src="https://cdn.jsdelivr.net/npm/regex@5.1.1/dist/regex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/regex-recursion@5.1.1/dist/regex-recursion.min.js"></script>
<script>
const {regex} = Regex;
const {recursion} = Regex.plugins;
const re = regex({plugins: [recursion]})`…`;
</script>
```
</details>
## Examples
### Match an equal number of two different subpatterns
#### Anywhere within a string
```js
// Matches sequences of up to 50 'a' chars followed by the same number of 'b'
const re = regex({plugins: [recursion]})`a(?R=50)?b`;
re.exec('test aaaaaabbb')[0];
// → 'aaabbb'
```
#### As the entire string
```js
const re = regex({plugins: [recursion]})`^
(?<balanced>
a
# Recursively match just the specified group
\g<balanced&R=50>?
b
)
$`;
re.test('aaabbb'); // → true
re.test('aaabb'); // → false
```
Notice the `^` and `$` anchors outside of the recursive subpattern.
### Match balanced parentheses
```js
// Matches all balanced parentheses up to depth 50
const parens = regex({flags: 'g', plugins: [recursion]})`
\( ( [^\(\)] | (?R=50) )* \)
`;
'test ) (balanced ((parens))) () ((a)) ( (b)'.match(parens);
/* → [
'(balanced ((parens)))',
'()',
'((a))',
'(b)'
] */
```
Following is an alternative that matches the same strings, but adds a nested quantifier. It then uses an atomic group to prevent this nested quantifier from creating the potential for [catastrophic backtracking](https://www.regular-expressions.info/catastrophic.html).
```js
const parens = regex({flags: 'g', plugins: [recursion]})`
\( ( (?> [^\(\)]+ ) | (?R=50) )* \)
`;
```
This matches sequences of non-parens in one step with the nested `+` quantifier, and avoids backtracking into these sequences by wrapping it with an atomic group `(?>…)`. Given that what the nested quantifier `+` matches overlaps with what the outer group can match with its `*` quantifier, the atomic group is important here. It avoids exponential backtracking when matching long strings with unbalanced parens.
[Atomic groups](https://github.com/slevithan/regex#atomic-groups) are provided by the base `regex` library.
### Match palindromes
#### Match palindromes anywhere within a string
```js
const palindromes = regex({flags: 'gi', plugins: [recursion]})`
(?<char> \w )
# Recurse, or match a lone unbalanced char in the middle
( (?R=15) | \w? )
\k<char>
`;
'Racecar, ABBA, and redivided'.match(palindromes);
// → ['Racecar', 'ABBA', 'edivide']
```
In the example above, the max length of matched palindromes is 31. That's because it sets the max recursion depth to 15 with `(?R=15)`. So, depth 15 × 2 chars (left + right) for each depth level + 1 optional unbalanced char in the middle = 31. To match longer palindromes, the max recursion depth can be increased to a max of 100, which would enable matching palindromes up to 201 characters long.
#### Match palindromes as complete words
```js
const palindromeWords = regex({flags: 'gi', plugins: [recursion]})`\b
(?<palindrome>
(?<char> \w )
( \g<palindrome&R=15> | \w? )
\k<char>
)
\b`;
'Racecar, ABBA, and redivided'.match(palindromeWords);
// → ['Racecar', 'ABBA']
```
Notice the `\b` word boundaries outside of the recursive subpattern.
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/regex-recursion?color=78C372
[npm-version-href]: https://npmjs.com/package/regex-recursion
[bundle-src]: https://img.shields.io/bundlejs/size/regex-recursion?color=78C372&label=minzip
[bundle-href]: https://bundlejs.com/?q=regex-recursion&treeshake=[*]

View File

@@ -0,0 +1,17 @@
var Regex;(Regex||={}).plugins=(()=>{var N=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var z=(e,t)=>{for(var n in t)N(e,n,{get:t[n],enumerable:!0})},H=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of W(t))!q.call(e,r)&&r!==n&&N(e,r,{get:()=>t[r],enumerable:!(a=P(t,r))||a.enumerable});return e};var Q=e=>H(N({},"__esModule",{value:!0}),e);var X={};z(X,{recursion:()=>K});var i=Object.freeze({DEFAULT:"DEFAULT",CHAR_CLASS:"CHAR_CLASS"});function I(e,t,n,a){let r=new RegExp(String.raw`${t}|(?<$skip>\[\^?|\\?.)`,"gsu"),s=[!1],u=0,o="";for(let c of e.matchAll(r)){let{0:f,groups:{$skip:p}}=c;if(!p&&(!a||a===i.DEFAULT==!u)){n instanceof Function?o+=n(c,{context:u?i.CHAR_CLASS:i.DEFAULT,negated:s[s.length-1]}):o+=n;continue}f[0]==="["?(u++,s.push(f[1]==="^")):f==="]"&&u&&(u--,s.pop()),o+=f}return o}function b(e,t,n,a){I(e,t,n,a)}function Z(e,t,n=0,a){if(!new RegExp(t,"su").test(e))return null;let r=new RegExp(`${t}|(?<$skip>\\\\?.)`,"gsu");r.lastIndex=n;let s=0,u;for(;u=r.exec(e);){let{0:o,groups:{$skip:c}}=u;if(!c&&(!a||a===i.DEFAULT==!s))return u;o==="["?s++:o==="]"&&s&&s--,r.lastIndex==u.index&&r.lastIndex++}return null}function k(e,t,n){return!!Z(e,t,0,n)}function T(e,t){let n=/\\?./gsu;n.lastIndex=t;let a=e.length,r=0,s=1,u;for(;u=n.exec(e);){let[o]=u;if(o==="[")r++;else if(r)o==="]"&&r--;else if(o==="(")s++;else if(o===")"&&(s--,!s)){a=u.index;break}}return e.slice(t,a)}var w="$E$";var L=String.raw`\(\?(?:[:=!>A-Za-z\-]|<[=!]|\(DEFINE\))`;var ce=new RegExp(String.raw`(?<noncapturingStart>${L})|(?<capturingStart>\((?:\?<[^>]+>)?)|\\?.`,"gsu");var j=String.raw`(?:[?*+]|\{\d+(?:,\d*)?\})`,ie=new RegExp(String.raw`
\\(?: \d+
| c[A-Za-z]
| [gk]<[^>]+>
| [pPu]\{[^\}]+\}
| u[A-Fa-f\d]{4}
| x[A-Fa-f\d]{2}
)
| \((?: \? (?: [:=!>]
| <(?:[=!]|[^>]+>)
| [A-Za-z\-]+:
| \(DEFINE\)
))?
| (?<qBase>${j})(?<qMod>[?+]?)(?<invalidQ>[?*+\{]?)
| \\?.
`.replace(/\s+/g,""),"gsu");var l=String.raw,J=l`\\g<(?<gRNameOrNum>[^>&]+)&R=(?<gRDepth>[^>]+)>`,U=l`\(\?R=(?<rDepth>[^\)]+)\)|${J}`,R=l`\(\?<(?![=!])(?<captureName>[^>]+)>`,g=new RegExp(l`${R}|${U}|\(\?|\\?.`,"gsu"),G="Cannot use multiple overlapping recursions",M=new RegExp(l`(?:\$[1-9]\d*)?${w.replace(/\$/g,l`\$`)}`,"y");function K(e,t){if(!new RegExp(U,"su").test(e))return e;if(k(e,l`\(\?\(DEFINE\)`,i.DEFAULT))throw new Error("DEFINE groups cannot be used with recursion");let n=!!t?.useEmulationGroups,a=k(e,l`\\[1-9]`,i.DEFAULT),r=new Map,s=[],u=!1,o=0,c=0,f;for(g.lastIndex=0;f=g.exec(e);){let{0:p,groups:{captureName:A,rDepth:$,gRNameOrNum:d,gRDepth:E}}=f;if(p==="[")o++;else if(o)p==="]"&&o--;else if($){if(_($),u)throw new Error(G);if(a)throw new Error("Numbered backrefs cannot be used with global recursion");let h=e.slice(0,f.index),m=e.slice(g.lastIndex);if(k(m,U,i.DEFAULT))throw new Error(G);return v(h,m,+$,!1,n)}else if(d){_(E);let h=!1;for(let C of s)if(C.name===d||C.num===+d){if(h=!0,C.hasRecursedWithin)throw new Error(G);break}if(!h)throw new Error(l`Recursive \g cannot be used outside the referenced group "\g<${d}&R=${E}>"`);let m=r.get(d),x=T(e,m);if(a&&k(x,l`${R}|\((?!\?)`,i.DEFAULT))throw new Error("Numbered backrefs cannot be used with recursion of capturing groups");let D=e.slice(m,f.index),S=x.slice(D.length+p.length),F=v(D,S,+E,!0,n),O=e.slice(0,m),y=e.slice(m+x.length);e=`${O}${F}${y}`,g.lastIndex+=F.length-p.length-D.length-S.length,s.forEach(C=>C.hasRecursedWithin=!0),u=!0}else if(A)c++,r.set(String(c),g.lastIndex),r.set(A,g.lastIndex),s.push({num:c,name:A});else if(p.startsWith("(")){let h=p==="(";h&&(c++,r.set(String(c),g.lastIndex+(n?V(e,g.lastIndex):0))),s.push(h?{num:c}:{})}else p===")"&&s.pop()}return e}function _(e){let t=`Max depth must be integer between 2 and 100; used ${e}`;if(!/^[1-9]\d*$/.test(e))throw new Error(t);if(e=+e,e<2||e>100)throw new Error(t)}function v(e,t,n,a,r){let s=new Set;a&&b(e+t,R,({groups:{captureName:o}})=>{s.add(o)},i.DEFAULT);let u=n-1;return`${e}${B(`(?:${e}`,u,a?s:null,"forward",r)}(?:)${B(`${t})`,u,a?s:null,"backward",r)}${t}`}function B(e,t,n,a,r){let u=c=>a==="backward"?t-c+2-1:c+2,o="";for(let c=0;c<t;c++){let f=u(c);o+=I(e,l`${R}|\\k<(?<backref>[^>]+)>${r?l`|(?<unnamed>\()(?!\?)(?:${M.source})?`:""}`,({0:p,index:A,groups:{captureName:$,backref:d,unnamed:E}})=>{if(d&&n&&!n.has(d))return p;if(E)return`(${w}`;let h=`_$${f}`;return $?`(?<${$}${h}>${r?w:""}`:l`\k<${d}${h}>`},i.DEFAULT)}return o}function V(e,t){M.lastIndex=t;let n=M.exec(e);return n?n[0].length:0}return Q(X);})();
//# sourceMappingURL=regex-recursion.min.js.map

File diff suppressed because one or more lines are too long

48
node_modules/regex-recursion/package.json generated vendored Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "regex-recursion",
"version": "5.1.1",
"description": "Recursive matching plugin for Regex+",
"author": "Steven Levithan",
"license": "MIT",
"type": "module",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./src/index.js"
}
},
"browser": "./dist/regex-recursion.min.js",
"types": "./types/index.d.ts",
"scripts": {
"bundle:global": "esbuild src/index.js --global-name=Regex.plugins --bundle --minify --sourcemap --outfile=dist/regex-recursion.min.js",
"types": "tsc src/index.js --rootDir src --declaration --allowJs --emitDeclarationOnly --outDir types",
"prebuild": "rm -rf dist/* types/*",
"build": "pnpm run bundle:global && pnpm run types",
"pretest": "pnpm run build",
"test": "jasmine",
"prepare": "pnpm test"
},
"files": [
"dist",
"src",
"types"
],
"repository": {
"type": "git",
"url": "git+https://github.com/slevithan/regex-recursion.git"
},
"keywords": [
"recursion",
"regex",
"regexp"
],
"dependencies": {
"regex": "^5.1.1",
"regex-utilities": "^2.3.0"
},
"devDependencies": {
"esbuild": "^0.24.2",
"jasmine": "^5.5.0",
"typescript": "~5.7.2"
}
}

221
node_modules/regex-recursion/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,221 @@
import {Context, forEachUnescaped, getGroupContents, hasUnescaped, replaceUnescaped} from 'regex-utilities';
import {emulationGroupMarker} from 'regex/internals';
const r = String.raw;
const gRToken = r`\\g<(?<gRNameOrNum>[^>&]+)&R=(?<gRDepth>[^>]+)>`;
const recursiveToken = r`\(\?R=(?<rDepth>[^\)]+)\)|${gRToken}`;
const namedCapturingDelim = r`\(\?<(?![=!])(?<captureName>[^>]+)>`;
const token = new RegExp(r`${namedCapturingDelim}|${recursiveToken}|\(\?|\\?.`, 'gsu');
const overlappingRecursionMsg = 'Cannot use multiple overlapping recursions';
// Support emulation groups with transfer marker prefix
const emulationGroupMarkerRe = new RegExp(r`(?:\$[1-9]\d*)?${emulationGroupMarker.replace(/\$/g, r`\$`)}`, 'y');
/**
@param {string} expression
@param {{
flags?: string;
useEmulationGroups?: boolean;
}} [data]
@returns {string}
*/
export function recursion(expression, data) {
// Keep the initial fail-check (which avoids unneeded processing) as fast as possible by testing
// without the accuracy improvement of using `hasUnescaped` with default `Context`
if (!(new RegExp(recursiveToken, 'su').test(expression))) {
return expression;
}
if (hasUnescaped(expression, r`\(\?\(DEFINE\)`, Context.DEFAULT)) {
throw new Error('DEFINE groups cannot be used with recursion');
}
const useEmulationGroups = !!data?.useEmulationGroups;
const hasNumberedBackref = hasUnescaped(expression, r`\\[1-9]`, Context.DEFAULT);
const groupContentsStartPos = new Map();
const openGroups = [];
let hasRecursed = false;
let numCharClassesOpen = 0;
let numCaptures = 0;
let match;
token.lastIndex = 0;
while ((match = token.exec(expression))) {
const {0: m, groups: {captureName, rDepth, gRNameOrNum, gRDepth}} = match;
if (m === '[') {
numCharClassesOpen++;
} else if (!numCharClassesOpen) {
// `(?R=N)`
if (rDepth) {
assertMaxInBounds(rDepth);
if (hasRecursed) {
throw new Error(overlappingRecursionMsg);
}
if (hasNumberedBackref) {
// Could add support for numbered backrefs with extra effort, but it's probably not worth
// it. To trigger this error, the regex must include recursion and one of the following:
// - An interpolated regex that contains a numbered backref (since other numbered
// backrefs are prevented by implicit flag n).
// - A numbered backref, when flag n is explicitly disabled.
// Note that Regex+'s extended syntax (atomic groups and sometimes subroutines) can also
// add numbered backrefs, but those work fine because external plugins like this one run
// *before* the transformation of built-in syntax extensions
throw new Error('Numbered backrefs cannot be used with global recursion');
}
const pre = expression.slice(0, match.index);
const post = expression.slice(token.lastIndex);
if (hasUnescaped(post, recursiveToken, Context.DEFAULT)) {
throw new Error(overlappingRecursionMsg);
}
// No need to parse further
return makeRecursive(pre, post, +rDepth, false, useEmulationGroups);
// `\g<name&R=N>`, `\g<number&R=N>`
} else if (gRNameOrNum) {
assertMaxInBounds(gRDepth);
let isWithinReffedGroup = false;
for (const g of openGroups) {
if (g.name === gRNameOrNum || g.num === +gRNameOrNum) {
isWithinReffedGroup = true;
if (g.hasRecursedWithin) {
throw new Error(overlappingRecursionMsg);
}
break;
}
}
if (!isWithinReffedGroup) {
throw new Error(r`Recursive \g cannot be used outside the referenced group "\g<${gRNameOrNum}&R=${gRDepth}>"`);
}
const startPos = groupContentsStartPos.get(gRNameOrNum);
const groupContents = getGroupContents(expression, startPos);
if (
hasNumberedBackref &&
hasUnescaped(groupContents, r`${namedCapturingDelim}|\((?!\?)`, Context.DEFAULT)
) {
throw new Error('Numbered backrefs cannot be used with recursion of capturing groups');
}
const groupContentsPre = expression.slice(startPos, match.index);
const groupContentsPost = groupContents.slice(groupContentsPre.length + m.length);
const expansion = makeRecursive(groupContentsPre, groupContentsPost, +gRDepth, true, useEmulationGroups);
const pre = expression.slice(0, startPos);
const post = expression.slice(startPos + groupContents.length);
// Modify the string we're looping over
expression = `${pre}${expansion}${post}`;
// Step forward for the next loop iteration
token.lastIndex += expansion.length - m.length - groupContentsPre.length - groupContentsPost.length;
openGroups.forEach(g => g.hasRecursedWithin = true);
hasRecursed = true;
} else if (captureName) {
numCaptures++;
// NOTE: Not currently handling *named* emulation groups that already exist in the pattern
groupContentsStartPos.set(String(numCaptures), token.lastIndex);
groupContentsStartPos.set(captureName, token.lastIndex);
openGroups.push({
num: numCaptures,
name: captureName,
});
} else if (m.startsWith('(')) {
const isUnnamedCapture = m === '(';
if (isUnnamedCapture) {
numCaptures++;
groupContentsStartPos.set(
String(numCaptures),
token.lastIndex + (useEmulationGroups ? emulationGroupMarkerLength(expression, token.lastIndex) : 0)
);
}
openGroups.push(isUnnamedCapture ? {num: numCaptures} : {});
} else if (m === ')') {
openGroups.pop();
}
} else if (m === ']') {
numCharClassesOpen--;
}
}
return expression;
}
/**
@param {string} max
*/
function assertMaxInBounds(max) {
const errMsg = `Max depth must be integer between 2 and 100; used ${max}`;
if (!/^[1-9]\d*$/.test(max)) {
throw new Error(errMsg);
}
max = +max;
if (max < 2 || max > 100) {
throw new Error(errMsg);
}
}
/**
@param {string} pre
@param {string} post
@param {number} maxDepth
@param {boolean} isSubpattern
@param {boolean} useEmulationGroups
@returns {string}
*/
function makeRecursive(pre, post, maxDepth, isSubpattern, useEmulationGroups) {
const namesInRecursed = new Set();
// Avoid this work if not needed
if (isSubpattern) {
forEachUnescaped(pre + post, namedCapturingDelim, ({groups: {captureName}}) => {
namesInRecursed.add(captureName);
}, Context.DEFAULT);
}
const reps = maxDepth - 1;
// Depth 2: 'pre(?:pre(?:)post)post'
// Depth 3: 'pre(?:pre(?:pre(?:)post)post)post'
return `${pre}${
repeatWithDepth(`(?:${pre}`, reps, (isSubpattern ? namesInRecursed : null), 'forward', useEmulationGroups)
}(?:)${
repeatWithDepth(`${post})`, reps, (isSubpattern ? namesInRecursed : null), 'backward', useEmulationGroups)
}${post}`;
}
/**
@param {string} expression
@param {number} reps
@param {Set<string> | null} namesInRecursed
@param {'forward' | 'backward'} direction
@param {boolean} useEmulationGroups
@returns {string}
*/
function repeatWithDepth(expression, reps, namesInRecursed, direction, useEmulationGroups) {
const startNum = 2;
const depthNum = i => direction === 'backward' ? reps - i + startNum - 1 : i + startNum;
let result = '';
for (let i = 0; i < reps; i++) {
const captureNum = depthNum(i);
result += replaceUnescaped(
expression,
// NOTE: Not currently handling *named* emulation groups that already exist in the pattern
r`${namedCapturingDelim}|\\k<(?<backref>[^>]+)>${
useEmulationGroups ? r`|(?<unnamed>\()(?!\?)(?:${emulationGroupMarkerRe.source})?` : ''
}`,
({0: m, index, groups: {captureName, backref, unnamed}}) => {
if (backref && namesInRecursed && !namesInRecursed.has(backref)) {
// Don't alter backrefs to groups outside the recursed subpattern
return m;
}
// Only matches unnamed capture delim if `useEmulationGroups`
if (unnamed) {
// Add an emulation group marker, possibly replacing an existing marker (removes any
// transfer prefix)
return `(${emulationGroupMarker}`;
}
const suffix = `_$${captureNum}`;
return captureName ?
`(?<${captureName}${suffix}>${useEmulationGroups ? emulationGroupMarker : ''}` :
r`\k<${backref}${suffix}>`;
},
Context.DEFAULT
);
}
return result;
}
function emulationGroupMarkerLength(expression, index) {
emulationGroupMarkerRe.lastIndex = index;
const match = emulationGroupMarkerRe.exec(expression);
return match ? match[0].length : 0;
}

12
node_modules/regex-recursion/types/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,12 @@
/**
@param {string} expression
@param {{
flags?: string;
useEmulationGroups?: boolean;
}} [data]
@returns {string}
*/
export function recursion(expression: string, data?: {
flags?: string;
useEmulationGroups?: boolean;
}): string;