Skip to content

Commit

Permalink
Add pathToRegexp method back
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeembrey committed Sep 10, 2024
1 parent a43e545 commit d6150f5
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 98 deletions.
25 changes: 16 additions & 9 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ npm install path-to-regexp --save
## Usage

```js
const { match, compile, parse } = require("path-to-regexp");

// match(path, options?)
// compile(path, options?)
// parse(path, options?)
const { match, pathToRegexp, compile, parse } = require("path-to-regexp");
```

### Parameters
Expand Down Expand Up @@ -64,20 +60,31 @@ fn("/users/123/delete");

The `match` function returns a function for matching strings against a path:

- **path** String or array of strings.
- **options** _(optional)_ (Extends [pathToRegexp](#pathToRegexp) options)
- **decode** Function for decoding strings to params, or `false` to disable all processing. (default: `decodeURIComponent`)

```js
const fn = match("/foo/:bar");
```

**Please note:** `path-to-regexp` is intended for ordered data (e.g. paths, hosts). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).

## PathToRegexp

The `pathToRegexp` function returns a regular expression for matching strings against paths. It

- **path** String or array of strings.
- **options** _(optional)_ (See [parse](#parse) for more options)
- **sensitive** Regexp will be case sensitive. (default: `false`)
- **end** Validate the match reaches the end of the string. (default: `true`)
- **delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` parameters. (default: `'/'`)
- **trailing** Allows optional trailing delimiter to match. (default: `true`)
- **decode** Function for decoding strings to params, or `false` to disable all processing. (default: `decodeURIComponent`)

```js
const fn = match("/foo/:bar");
const { regexp, keys } = pathToRegexp("/foo/:bar");
```

**Please note:** `path-to-regexp` is intended for ordered data (e.g. pathnames, hostnames). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).

## Compile ("Reverse" Path-To-RegExp)

The `compile` function will return a function for transforming parameters into a valid path:
Expand Down
10 changes: 5 additions & 5 deletions scripts/redos.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { checkSync } from "recheck";
import { match } from "../src/index.js";
import { pathToRegexp } from "../src/index.js";
import { MATCH_TESTS } from "../src/cases.spec.js";

let safe = 0;
Expand All @@ -8,14 +8,14 @@ let fail = 0;
const TESTS = MATCH_TESTS.map((x) => x.path);

for (const path of TESTS) {
const { re } = match(path) as any;
const result = checkSync(re.source, re.flags);
const { regexp } = pathToRegexp(path);
const result = checkSync(regexp.source, regexp.flags);
if (result.status === "safe") {
safe++;
console.log("Safe:", path, String(re));
console.log("Safe:", path, String(regexp));
} else {
fail++;
console.log("Fail:", path, String(re));
console.log("Fail:", path, String(regexp));
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/cases.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ export const MATCH_TESTS: MatchTestSet[] = [
input: "/test/",
expected: { path: "/test/", params: {} },
},
{
input: "/TEST/",
expected: { path: "/TEST/", params: {} },
},
],
},
{
Expand Down Expand Up @@ -394,11 +398,11 @@ export const MATCH_TESTS: MatchTestSet[] = [
sensitive: true,
},
tests: [
{ input: "/test", expected: false },
{
input: "/TEST",
expected: { path: "/TEST", params: {} },
},
{ input: "/test", expected: false },
],
},

Expand Down
158 changes: 75 additions & 83 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ export interface ParseOptions {
encodePath?: Encode;
}

export interface MatchOptions {
/**
* Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
*/
decode?: Decode | false;
export interface PathToRegexpOptions {
/**
* Matches the path completely without trailing characters. (default: `true`)
*/
Expand All @@ -44,6 +40,13 @@ export interface MatchOptions {
delimiter?: string;
}

export interface MatchOptions extends PathToRegexpOptions {
/**
* Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
*/
decode?: Decode | false;
}

export interface CompileOptions {
/**
* Function for encoding input strings for output into the path, or `false` to disable entirely. (default: `encodeURIComponent`)
Expand Down Expand Up @@ -109,13 +112,6 @@ function escape(str: string) {
return str.replace(/[.+*?^${}()[\]|/\\]/g, "\\$&");
}

/**
* Get the flags for a regexp from the options.
*/
function toFlags(options: { sensitive?: boolean }) {
return options.sensitive ? "s" : "is";
}

/**
* Tokenize input string.
*/
Expand Down Expand Up @@ -253,6 +249,16 @@ export interface Group {
tokens: Token[];
}

/**
* A token that corresponds with a regexp capture.
*/
export type Key = Parameter | Wildcard;

/**
* A sequence of `path-to-regexp` keys that match capturing groups.
*/
export type Keys = Array<Key>;

/**
* A sequence of path match characters.
*/
Expand Down Expand Up @@ -316,14 +322,15 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
}

/**
* Transform tokens into a path building function.
* Compile a string to a template function for the path.
*/
function $compile<P extends ParamData>(
data: TokenData,
options: CompileOptions,
): PathFunction<P> {
export function compile<P extends ParamData = ParamData>(
path: Path,
options: CompileOptions & ParseOptions = {},
) {
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: P = {} as P) {
Expand All @@ -335,19 +342,6 @@ function $compile<P extends ParamData>(
};
}

/**
* Compile a string to a template function for the path.
*/
export function compile<P extends ParamData = ParamData>(
path: Path,
options: CompileOptions & ParseOptions = {},
) {
return $compile<P>(
path instanceof TokenData ? path : parse(path, options),
options,
);
}

export type ParamData = Partial<Record<string, string | string[]>>;
export type PathFunction<P extends ParamData> = (data?: P) => string;

Expand Down Expand Up @@ -451,75 +445,77 @@ export type Match<P extends ParamData> = false | MatchResult<P>;
export type MatchFunction<P extends ParamData> = (path: string) => Match<P>;

/**
* Create path match function from `path-to-regexp` spec.
* Supported path types.
*/
function $match<P extends ParamData>(
data: TokenData[],
options: MatchOptions = {},
): MatchFunction<P> {
const {
decode = decodeURIComponent,
delimiter = DEFAULT_DELIMITER,
end = true,
trailing = true,
} = options;
const flags = toFlags(options);
const sources: string[] = [];
const keys: Array<Parameter | Wildcard> = [];

for (const { tokens } of data) {
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)}|$)`;
export type Path = string | TokenData;

const re = new RegExp(pattern, flags);
/**
* Transform a path into a match function.
*/
export function match<P extends ParamData>(
path: Path | Path[],
options: MatchOptions & ParseOptions = {},
): MatchFunction<P> {
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: string) => value.split(delimiter).map(decode);
});

return Object.assign(
function match(input: string) {
const m = re.exec(input);
if (!m) return false;
return function match(input: string) {
const m = regexp.exec(input);
if (!m) return false;

const { 0: path } = m;
const params = Object.create(null);
const path = m[0];
const params = Object.create(null);

for (let i = 1; i < m.length; i++) {
if (m[i] === undefined) continue;
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]);
}
const key = keys[i - 1];
const decoder = decoders[i - 1];
params[key.name] = decoder(m[i]);
}

return { path, params };
},
{ re },
);
return { path, params };
};
}

export type Path = string | TokenData;

export function match<P extends ParamData>(
export function pathToRegexp(
path: Path | Path[],
options: MatchOptions & ParseOptions = {},
): MatchFunction<P> {
options: PathToRegexpOptions & ParseOptions = {},
) {
const {
delimiter = DEFAULT_DELIMITER,
end = true,
sensitive = false,
trailing = true,
} = options;
const keys: Keys = [];
const sources: string[] = [];
const flags = sensitive ? "s" : "is";
const paths = Array.isArray(path) ? path : [path];
const items = paths.map((path) =>
path instanceof TokenData ? path : parse(path, options),
);

return $match(items, 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 };
}

/**
Expand Down Expand Up @@ -556,11 +552,7 @@ function* flatten(
/**
* Transform a flat sequence of tokens into a regular expression.
*/
function sequenceToRegExp(
tokens: Flattened[],
delimiter: string,
keys: Array<Parameter | Wildcard>,
): string {
function sequenceToRegExp(tokens: Flattened[], delimiter: string, keys: Keys) {
let result = "";
let backtrack = "";
let isSafeSegmentParam = true;
Expand Down

0 comments on commit d6150f5

Please sign in to comment.