Skip to content

Commit

Permalink
Use optional groups
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeembrey committed Aug 25, 2024
1 parent 43cbe49 commit e9a7e01
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 65 deletions.
22 changes: 11 additions & 11 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ fn("/bar/baz");
//=> { path: '/bar/baz', params: { splat: [ 'bar', 'baz' ] } }
```

### Braces
### Optional

Simple brace expansion can be used to define multiple versions of a patch to match. It's also an effective way to create optional things to match.
Braces can be used to define parts of the path that are optional.

```js
const fn = match("/{a,b,:other}");
const fn = match("/users{/:id}/delete");

fn("/a");
//=> { path: '/a', params: {} }
fn("/users/delete");
//=> { path: '/users/delete', params: {} }

fn("/c");
//=> { path: '/c', params: { other: 'c' } }
fn("/users/123/delete");
//=> { path: '/users/123/delete', params: { id: '123' } }
```

## Match
Expand Down Expand Up @@ -146,9 +146,9 @@ An effort has been made to ensure ambiguous paths from previous releases throw a

In past releases, `?`, `*`, and `+` were used to denote optional or repeating parameters. As an alternative, try these:

- For optional (`?`), use an empty segment in a group such as `/:file{.:ext,}`.
- For repeating (`+`), only wildcard matching is supported, such as `/*glob`.
- For optional repeating (`*`), use a group and a wildcard parameter such as `/{*glob,}`.
- For optional (`?`), use an empty segment in a group such as `/:file{.:ext}`.
- For repeating (`+`), only wildcard matching is supported, such as `/*path`.
- For optional repeating (`*`), use a group and a wildcard parameter such as `/files{/*path}`.

### Unexpected `(`, `)`, `[`, `]`, etc.

Expand All @@ -167,7 +167,7 @@ Parameter names can be wrapped in double quote characters, and this error means
Path-To-RegExp breaks compatibility with Express <= `4.x` in the following ways:

- Regexp characters can no longer be provided.
- The optional character `?` is no longer supported, use brace expansion instead: `/:file{.:ext,}`.
- The optional character `?` is no longer supported, use braces instead: `/:file{.:ext}`.
- Some characters have new meaning or have been reserved (`{}?*+@!;`).
- The parameter name now supports all JavaScript identifier characters, previously it was only `[a-z0-9]`.

Expand Down
13 changes: 3 additions & 10 deletions scripts/redos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,9 @@ const TESTS = [
"/*foo/:bar/*baz",
"/@:foo-:baz@",
"/:foo{.:ext}",
"/:foo{.:ext,}",
"/:foo{|:ext|,}",
"/:foo{/:bar,}/:baz",
"/user{,s}/:id",
"/user/{en,de,cn}/:id",
"/user/{en,de,cn}/{1,2,3}",
"/user/{en,de,cn/{1,2,3}}",
"/user/{en,de,cn/{}}",
"/user/{en,de,cn/{}/test}",
"/user/{en,de,cn}/{x}",
"/:foo{|:ext|}",
"/:foo{/:bar}/:baz",
"/user{s}/:id",
"/books/*section/:title",
];

Expand Down
2 changes: 1 addition & 1 deletion src/cases.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2229,7 +2229,7 @@ export const MATCH_TESTS: MatchTestSet[] = [
* Multi character delimiters.
*/
{
path: "%25:foo{%25:bar,}",
path: "%25:foo{%25:bar}",
options: {
delimiter: "%25",
},
Expand Down
53 changes: 10 additions & 43 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export interface CompileOptions extends PathOptions {
type TokenType =
| "{"
| "}"
| ","
| "WILDCARD"
| "PARAM"
| "CHAR"
Expand Down Expand Up @@ -89,7 +88,6 @@ const SIMPLE_TOKENS: Record<string, TokenType> = {
// Groups.
"{": "{",
"}": "}",
",": ",",
// Reserved.
"(": "(",
")": ")",
Expand Down Expand Up @@ -248,7 +246,7 @@ export interface Wildcard {
*/
export interface Group {
type: "group";
children: Array<Token[]>;
tokens: Token[];
}

/**
Expand Down Expand Up @@ -284,7 +282,7 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
options;
const it = new Iter(lexer(str));

function consume(brace: boolean): [end: boolean, tokens: Token[]] {
function consume(endType: TokenType): Token[] {
const tokens: Token[] = [];

while (true) {
Expand All @@ -311,34 +309,19 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {

const open = it.tryConsume("{");
if (open) {
const children: Array<Token[]> = [];

while (true) {
const [end, tokens] = consume(true);
children.push(tokens);
if (end) break;
}

tokens.push({
type: "group",
children: children,
tokens: consume("}"),
});
continue;
}

if (brace) {
const comma = it.tryConsume(",");
if (comma) return [false, tokens];
it.consume("}");
} else {
it.consume("END");
}

return [true, tokens];
it.consume(endType);
return tokens;
}
}

const [, tokens] = consume(false);
const tokens = consume("END");
return new TokenData(tokens, delimiter);
}

Expand Down Expand Up @@ -408,19 +391,7 @@ function tokenToFunction(
if (token.type === "text") return () => [token.value];

if (token.type === "group") {
const fns = token.children.map((child) =>
tokensToFunction(child, delimiter, encode),
);

return (data) => {
const allMissing: string[] = [];
for (const fn of fns) {
const [value, ...missing] = fn(data);
if (!missing.length) return [value];
allMissing.push(...missing);
}
return ["", ...allMissing];
};
return tokensToFunction(token.tokens, delimiter, encode);
}

const encodeValue = encode || NOOP_VALUE;
Expand Down Expand Up @@ -569,14 +540,10 @@ function* flatten(
const token = tokens[index];

if (token.type === "group") {
for (const child of token.children) {
const fork = init.slice();
for (const seq of flatten(child, 0, fork)) {
yield* flatten(tokens, index + 1, seq);
}
const fork = init.slice();
for (const seq of flatten(token.tokens, 0, fork)) {
yield* flatten(tokens, index + 1, seq);
}

if (token.children.length) return;
} else {
init.push(token);
}
Expand Down

0 comments on commit e9a7e01

Please sign in to comment.