Skip to content

Commit d94a276

Browse files
committed
Major changes
* New feature! Ability to set `endsWith` to match paths like `/test?query=string` and ignore the query string * Remove `isarray` * Explicitly handle trailing delimiters instead of trimming them (e.g. `/test/` is now treated as `/test/` instead of `/test` when matching) * Reverse `keys` and `options` arguments - functions were previously overloaded to accept `keys` and `options` as the second argument
1 parent e97122d commit d94a276

File tree

7 files changed

+158
-116
lines changed

7 files changed

+158
-116
lines changed

.travis.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
sudo: false
12
language: node_js
23

34
node_js:
4-
- "0.10"
5-
- "0.12"
65
- "4.0"
7-
- "4.1"
6+
- "stable"
87

98
after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"

Readme.md

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Path-to-RegExp
22

3-
> Turn an Express-style path string such as `/user/:name` into a regular expression.
3+
> Turn a path string such as `/user/:name` into a regular expression.
44
55
[![NPM version][npm-image]][npm-url]
66
[![Build status][travis-image]][travis-url]
@@ -20,38 +20,39 @@ npm install path-to-regexp --save
2020
```javascript
2121
var pathToRegexp = require('path-to-regexp')
2222

23-
// pathToRegexp(path, keys, options)
23+
// pathToRegexp(path, options?, keys?)
2424
// pathToRegexp.parse(path)
2525
// pathToRegexp.compile(path)
2626
```
2727

28-
- **path** An Express-style string, an array of strings, or a regular expression.
29-
- **keys** An array to be populated with the keys found in the path.
28+
- **path** A string, array of strings, or a regular expression.
3029
- **options**
3130
- **sensitive** When `true` the route will be case sensitive. (default: `false`)
3231
- **strict** When `false` the trailing slash is optional. (default: `false`)
3332
- **end** When `false` the path will match at the beginning. (default: `true`)
3433
- **delimiter** Set the default delimiter for repeat parameters. (default: `'/'`)
34+
- **endsWith** Optional character, or list of characters, to treat as "end" characters
35+
- **keys** An (optional) array to be populated with the keys found in the path. (also returned as `RegExp#keys`)
3536

3637
```javascript
3738
var keys = []
38-
var re = pathToRegexp('/foo/:bar', keys)
39+
var re = pathToRegexp('/foo/:bar', null, keys)
3940
// re = /^\/foo\/([^\/]+?)\/?$/i
4041
// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
4142
```
4243

43-
**Please note:** The `RegExp` returned by `path-to-regexp` is intended for use with pathnames or hostnames. It can not handle the query strings or fragments of a URL.
44+
**Please note:** The `RegExp` returned by `path-to-regexp` is intended for ordered data (e.g. pathnames, hostnames). It does not handle arbitrary data (e.g. query strings, URL fragments, JSON, etc).
4445

4546
### Parameters
4647

47-
The path string can be used to define parameters and populate the keys.
48+
The path argument is used to define parameters and populate the list of keys.
4849

4950
#### Named Parameters
5051

5152
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). By default, the parameter will match until the following path segment.
5253

5354
```js
54-
var re = pathToRegexp('/:foo/:bar', keys)
55+
var re = pathToRegexp('/:foo/:bar')
5556
// keys = [{ name: 'foo', prefix: '/', ... }, { name: 'bar', prefix: '/', ... }]
5657

5758
re.exec('/test/route')
@@ -61,21 +62,21 @@ re.exec('/test/route')
6162
**Please note:** Named parameters must be made up of "word characters" (`[A-Za-z0-9_]`).
6263

6364
```js
64-
var re = pathToRegexp('/(apple-)?icon-:res(\\d+).png', keys)
65+
var re = pathToRegexp('/(apple-)?icon-:res(\\d+).png')
6566
// keys = [{ name: 0, prefix: '/', ... }, { name: 'res', prefix: '', ... }]
6667

6768
re.exec('/icon-76.png')
6869
//=> ['/icon-76.png', undefined, '76']
6970
```
7071

71-
#### Modified Parameters
72+
#### Parameter Modifiers
7273

7374
##### Optional
7475

75-
Parameters can be suffixed with a question mark (`?`) to make the parameter optional. This will also make the prefix optional.
76+
Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
7677

7778
```js
78-
var re = pathToRegexp('/:foo/:bar?', keys)
79+
var re = pathToRegexp('/:foo/:bar?')
7980
// keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }]
8081

8182
re.exec('/test')
@@ -85,12 +86,14 @@ re.exec('/test/route')
8586
//=> ['/test', 'test', 'route']
8687
```
8788

89+
**Tip:** If the parameter is the _only_ value in the segment, the prefix is also optional.
90+
8891
##### Zero or more
8992

9093
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches. The prefix is taken into account for each match.
9194

9295
```js
93-
var re = pathToRegexp('/:foo*', keys)
96+
var re = pathToRegexp('/:foo*')
9497
// keys = [{ name: 'foo', delimiter: '/', optional: true, repeat: true }]
9598

9699
re.exec('/')
@@ -105,7 +108,7 @@ re.exec('/bar/baz')
105108
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches. The prefix is taken into account for each match.
106109

107110
```js
108-
var re = pathToRegexp('/:foo+', keys)
111+
var re = pathToRegexp('/:foo+')
109112
// keys = [{ name: 'foo', delimiter: '/', optional: false, repeat: true }]
110113

111114
re.exec('/')
@@ -120,7 +123,7 @@ re.exec('/bar/baz')
120123
All parameters can be provided a custom regexp, which overrides the default (`[^\/]+`).
121124

122125
```js
123-
var re = pathToRegexp('/:foo(\\d+)', keys)
126+
var re = pathToRegexp('/:foo(\\d+)')
124127
// keys = [{ name: 'foo', ... }]
125128

126129
re.exec('/123')
@@ -137,7 +140,7 @@ re.exec('/abc')
137140
It is possible to write an unnamed parameter that only consists of a matching group. It works the same as a named parameter, except it will be numerically indexed.
138141

139142
```js
140-
var re = pathToRegexp('/:foo/(.*)', keys)
143+
var re = pathToRegexp('/:foo/(.*)')
141144
// keys = [{ name: 'foo', ... }, { name: 0, ... }]
142145

143146
re.exec('/test/route')
@@ -149,7 +152,7 @@ re.exec('/test/route')
149152
An asterisk can be used for matching everything. It is equivalent to an unnamed matching group of `(.*)`.
150153

151154
```js
152-
var re = pathToRegexp('/foo/*', keys)
155+
var re = pathToRegexp('/foo/*')
153156
// keys = [{ name: '0', ... }]
154157

155158
re.exec('/foo/bar/baz')
@@ -173,11 +176,11 @@ console.log(tokens[2])
173176
//=> { name: 0, prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '.*' }
174177
```
175178

176-
**Note:** This method only works with Express-style strings.
179+
**Note:** This method only works with strings.
177180

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

180-
Path-To-RegExp exposes a compile function for transforming an Express-style path into a valid path.
183+
Path-To-RegExp exposes a compile function for transforming a string into a valid path.
181184

182185
```js
183186
var toPath = pathToRegexp.compile('/user/:id')
@@ -225,10 +228,8 @@ Path-To-RegExp exposes the two functions used internally that accept an array of
225228

226229
Path-To-RegExp breaks compatibility with Express <= `4.x`:
227230

228-
* No longer a direct conversion to a RegExp with sugar on top - it's a path matcher with named and unnamed matching groups
229-
* It's unlikely you previously abused this feature, it's rare and you could always use a RegExp instead
230-
* All matching RegExp special characters can be used in a matching group. E.g. `/:user(.*)`
231-
* Other RegExp features are not support - no nested matching groups, non-capturing groups or look aheads
231+
* RegExp special characters can only be used in a parameter
232+
* Express.js 4.x used all `RegExp` special characters regardless of position - this considered a bug
232233
* Parameters have suffixes that augment meaning - `*`, `+` and `?`. E.g. `/:user*`
233234

234235
## TypeScript

index.d.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
declare function pathToRegexp (path: pathToRegexp.Path, options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions): pathToRegexp.PathRegExp;
2-
declare function pathToRegexp (path: pathToRegexp.Path, keys?: pathToRegexp.Key[], options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions): pathToRegexp.PathRegExp;
1+
declare function pathToRegexp (path: pathToRegexp.Path, options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions, keys?: pathToRegexp.Key[]): pathToRegexp.PathRegExp;
32

43
declare namespace pathToRegexp {
54
export interface PathRegExp extends RegExp {
@@ -24,6 +23,10 @@ declare namespace pathToRegexp {
2423
* Sets the final character for non-ending optimistic matches. (default: `/`)
2524
*/
2625
delimiter?: string;
26+
/**
27+
* List of characters that can also be "end" characters.
28+
*/
29+
endsWith?: string | string[];
2730
}
2831

2932
export interface ParseOptions {
@@ -51,8 +54,7 @@ declare namespace pathToRegexp {
5154
/**
5255
* Transform an array of tokens into a matching regular expression.
5356
*/
54-
export function tokensToRegExp (tokens: Token[], options?: RegExpOptions): PathRegExp;
55-
export function tokensToRegExp (tokens: Token[], keys?: Key[], options?: RegExpOptions): PathRegExp;
57+
export function tokensToRegExp (tokens: Token[], options?: RegExpOptions, keys?: Key[]): PathRegExp;
5658

5759
export interface Key {
5860
name: string | number;

index.js

Lines changed: 30 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
var isarray = require('isarray')
2-
31
/**
42
* Expose `pathToRegexp`.
53
*/
@@ -39,10 +37,10 @@ function parse (str, options) {
3937
var key = 0
4038
var index = 0
4139
var path = ''
42-
var defaultDelimiter = options && options.delimiter || '/'
40+
var defaultDelimiter = (options && options.delimiter) || '/'
4341
var res
4442

45-
while ((res = PATH_REGEXP.exec(str)) != null) {
43+
while ((res = PATH_REGEXP.exec(str)) !== null) {
4644
var m = res[0]
4745
var escaped = res[1]
4846
var offset = res.index
@@ -69,7 +67,7 @@ function parse (str, options) {
6967
path = ''
7068
}
7169

72-
var partial = prefix != null && next != null && next !== prefix
70+
var partial = prefix !== undefined && next !== undefined && next !== prefix
7371
var repeat = modifier === '+' || modifier === '*'
7472
var optional = modifier === '?' || modifier === '*'
7573
var delimiter = res[2] || defaultDelimiter
@@ -118,7 +116,7 @@ function compile (str, options) {
118116
* @return {string}
119117
*/
120118
function encodeURIComponentPretty (str) {
121-
return encodeURI(str).replace(/[\/?#]/g, function (c) {
119+
return encodeURI(str).replace(/[/?#]/g, function (c) {
122120
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
123121
})
124122
}
@@ -180,7 +178,7 @@ function tokensToFunction (tokens) {
180178
}
181179
}
182180

183-
if (isarray(value)) {
181+
if (Array.isArray(value)) {
184182
if (!token.repeat) {
185183
throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
186184
}
@@ -226,7 +224,7 @@ function tokensToFunction (tokens) {
226224
* @return {string}
227225
*/
228226
function escapeString (str) {
229-
return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
227+
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1')
230228
}
231229

232230
/**
@@ -236,7 +234,7 @@ function escapeString (str) {
236234
* @return {string}
237235
*/
238236
function escapeGroup (group) {
239-
return group.replace(/([=!:$\/()])/g, '\\$1')
237+
return group.replace(/([=!:$/()])/g, '\\$1')
240238
}
241239

242240
/**
@@ -294,15 +292,15 @@ function regexpToRegexp (path, keys) {
294292
* Transform an array into a regexp.
295293
*
296294
* @param {!Array} path
297-
* @param {Array} keys
298295
* @param {!Object} options
296+
* @param {Array} keys
299297
* @return {!RegExp}
300298
*/
301-
function arrayToRegexp (path, keys, options) {
299+
function arrayToRegexp (path, options, keys) {
302300
var parts = []
303301

304302
for (var i = 0; i < path.length; i++) {
305-
parts.push(pathToRegexp(path[i], keys, options).source)
303+
parts.push(pathToRegexp(path[i], options, keys).source)
306304
}
307305

308306
var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options))
@@ -314,28 +312,24 @@ function arrayToRegexp (path, keys, options) {
314312
* Create a path regexp from string input.
315313
*
316314
* @param {string} path
317-
* @param {!Array} keys
318315
* @param {!Object} options
316+
* @param {!Array} keys
319317
* @return {!RegExp}
320318
*/
321-
function stringToRegexp (path, keys, options) {
322-
return tokensToRegExp(parse(path, options), keys, options)
319+
function stringToRegexp (path, options, keys) {
320+
return tokensToRegExp(parse(path, options), options, keys)
323321
}
324322

325323
/**
326324
* Expose a function for taking tokens and returning a RegExp.
327325
*
328-
* @param {!Array} tokens
329-
* @param {(Array|Object)=} keys
330-
* @param {Object=} options
326+
* @param {!Array} tokens
327+
* @param {Object=} options
328+
* @param {Array=} keys
331329
* @return {!RegExp}
332330
*/
333-
function tokensToRegExp (tokens, keys, options) {
334-
if (!isarray(keys)) {
335-
options = /** @type {!Object} */ (keys || options)
336-
keys = []
337-
}
338-
331+
function tokensToRegExp (tokens, options, keys) {
332+
keys = keys || []
339333
options = options || {}
340334

341335
var strict = options.strict
@@ -373,22 +367,19 @@ function tokensToRegExp (tokens, keys, options) {
373367
}
374368

375369
var delimiter = escapeString(options.delimiter || '/')
376-
var endsWithDelimiter = route.slice(-delimiter.length) === delimiter
370+
var endsWith = [].concat(options.endsWith || []).map(escapeString).concat('$').join('|')
377371

378-
// In non-strict mode we allow a slash at the end of match. If the path to
379-
// match already ends with a slash, we remove it for consistency. The slash
380-
// is valid at the end of a path match, not in the middle. This is important
381-
// in non-ending mode, where "/test/" shouldn't match "/test//route".
372+
// In non-strict mode we allow a delimiter at the end of a match.
382373
if (!strict) {
383-
route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'
374+
route += '(?:' + delimiter + '(?=' + endsWith + '))?'
384375
}
385376

386377
if (end) {
387-
route += '$'
378+
route += endsWith === '$' ? endsWith : '(?=' + endsWith + ')'
388379
} else {
389380
// In non-ending mode, we need the capturing groups to match as much as
390381
// possible by using a positive lookahead to the end or next path segment.
391-
route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'
382+
route += '(?=' + delimiter + '|' + endsWith + ')'
392383
}
393384

394385
return attachKeys(new RegExp('^' + route, flags(options)), keys)
@@ -402,25 +393,21 @@ function tokensToRegExp (tokens, keys, options) {
402393
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
403394
*
404395
* @param {(string|RegExp|Array)} path
405-
* @param {(Array|Object)=} keys
406396
* @param {Object=} options
397+
* @param {Array=} keys
407398
* @return {!RegExp}
408399
*/
409-
function pathToRegexp (path, keys, options) {
410-
if (!isarray(keys)) {
411-
options = /** @type {!Object} */ (keys || options)
412-
keys = []
413-
}
414-
400+
function pathToRegexp (path, options, keys) {
401+
keys = keys || []
415402
options = options || {}
416403

417404
if (path instanceof RegExp) {
418-
return regexpToRegexp(path, /** @type {!Array} */ (keys))
405+
return regexpToRegexp(path, keys)
419406
}
420407

421-
if (isarray(path)) {
422-
return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
408+
if (Array.isArray(path)) {
409+
return arrayToRegexp(/** @type {!Array} */ (path), options, keys)
423410
}
424411

425-
return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
412+
return stringToRegexp(/** @type {string} */ (path), options, keys)
426413
}

0 commit comments

Comments
 (0)