Skip to content

Commit b6eac3f

Browse files
authored
Remove partial token support, always delimiter (pillarjs#176)
1 parent d2a98c6 commit b6eac3f

File tree

4 files changed

+276
-180
lines changed

4 files changed

+276
-180
lines changed

Readme.md

+48-41
Original file line numberDiff line numberDiff line change
@@ -18,51 +18,50 @@ npm install path-to-regexp --save
1818
## Usage
1919

2020
```javascript
21-
var pathToRegexp = require('path-to-regexp')
21+
const pathToRegexp = require('path-to-regexp')
2222

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

2828
- **path** A string, array of strings, or a regular expression.
29-
- **keys** An array to be populated with keys found in the path.
29+
- **keys** An array to populate with keys found in the path.
3030
- **options**
3131
- **sensitive** When `true` the regexp will be case sensitive. (default: `false`)
3232
- **strict** When `true` the regexp allows an optional trailing delimiter to match. (default: `false`)
3333
- **end** When `true` the regexp will match to the end of the string. (default: `true`)
3434
- **start** When `true` the regexp will match from the beginning of the string. (default: `true`)
35-
- Advanced options (use for non-pathname strings, e.g. host names):
36-
- **delimiter** The default delimiter for segments. (default: `'/'`)
37-
- **endsWith** Optional character, or list of characters, to treat as "end" characters.
38-
- **delimiters** List of characters to consider delimiters when parsing. (default: `'./'`)
35+
- **delimiter** The default delimiter for segments. (default: `'/'`)
36+
- **endsWith** Optional character, or list of characters, to treat as "end" characters.
37+
- **whitelist** List of characters to consider delimiters when parsing. (default: `undefined`, any character)
3938

4039
```javascript
41-
var keys = []
42-
var re = pathToRegexp('/foo/:bar', keys)
43-
// re = /^\/foo\/([^\/]+?)\/?$/i
40+
const keys = []
41+
const regexp = pathToRegexp('/foo/:bar', keys)
42+
// regexp = /^\/foo\/([^\/]+?)\/?$/i
4443
// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
4544
```
4645

47-
**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).
46+
**Please note:** The `RegExp` returned by `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).
4847

4948
### Parameters
5049

5150
The path argument is used to define parameters and populate the list of keys.
5251

5352
#### Named Parameters
5453

55-
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). By default, the parameter will match until the following path segment.
54+
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). By default, the parameter will match until the next prefix (e.g. `[^/]+`).
5655

5756
```js
58-
var re = pathToRegexp('/:foo/:bar')
57+
const regexp = pathToRegexp('/:foo/:bar')
5958
// keys = [{ name: 'foo', prefix: '/', ... }, { name: 'bar', prefix: '/', ... }]
6059

6160
re.exec('/test/route')
6261
//=> ['/test/route', 'test', 'route']
6362
```
6463

65-
**Please note:** Parameter names must be made up of "word characters" (`[A-Za-z0-9_]`).
64+
**Please note:** Parameter names must use "word characters" (`[A-Za-z0-9_]`).
6665

6766
#### Parameter Modifiers
6867

@@ -71,7 +70,7 @@ re.exec('/test/route')
7170
Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
7271

7372
```js
74-
var re = pathToRegexp('/:foo/:bar?')
73+
const regexp = pathToRegexp('/:foo/:bar?')
7574
// keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }]
7675

7776
re.exec('/test')
@@ -81,14 +80,14 @@ re.exec('/test/route')
8180
//=> ['/test', 'test', 'route']
8281
```
8382

84-
**Tip:** If the parameter is the _only_ value in the segment, the prefix is also optional.
83+
**Tip:** The prefix is also optional, escape the prefix `\/` to make it required.
8584

8685
##### Zero or more
8786

88-
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches. The prefix is taken into account for each match.
87+
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches. The prefix is used for each match.
8988

9089
```js
91-
var re = pathToRegexp('/:foo*')
90+
const regexp = pathToRegexp('/:foo*')
9291
// keys = [{ name: 'foo', delimiter: '/', optional: true, repeat: true }]
9392

9493
re.exec('/')
@@ -100,10 +99,10 @@ re.exec('/bar/baz')
10099

101100
##### One or more
102101

103-
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.
102+
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches. The prefix is used for each match.
104103

105104
```js
106-
var re = pathToRegexp('/:foo+')
105+
const regexp = pathToRegexp('/:foo+')
107106
// keys = [{ name: 'foo', delimiter: '/', optional: false, repeat: true }]
108107

109108
re.exec('/')
@@ -113,41 +112,50 @@ re.exec('/bar/baz')
113112
//=> ['/bar/baz', 'bar/baz']
114113
```
115114

115+
#### Unnamed Parameters
116+
117+
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.
118+
119+
```js
120+
const regexp = pathToRegexp('/:foo/(.*)')
121+
// keys = [{ name: 'foo', ... }, { name: 0, ... }]
122+
123+
regexp.exec('/test/route')
124+
//=> ['/test/route', 'test', 'route']
125+
```
126+
116127
#### Custom Matching Parameters
117128

118-
All parameters can be provided a custom regexp, which overrides the default match (`[^\/]+`). For example, you can match digits in the path:
129+
All parameters can have a custom regexp, which overrides the default match (`[^/]+`). For example, you can match digits or names in a path:
119130

120131
```js
121-
var re = pathToRegexp('/icon-:foo(\\d+).png')
132+
const regexpNumbers = pathToRegexp('/icon-:foo(\\d+).png')
122133
// keys = [{ name: 'foo', ... }]
123134

124-
re.exec('/icon-123.png')
135+
regexpNumbers.exec('/icon-123.png')
125136
//=> ['/icon-123.png', '123']
126137

127-
re.exec('/icon-abc.png')
138+
regexpNumbers.exec('/icon-abc.png')
128139
//=> null
129-
```
130140

131-
**Please note:** Backslashes need to be escaped with another backslash in strings.
141+
const regexpWord = pathToRegexp('/(user|u)')
142+
// keys = [{ name: 0, ... }]
132143

133-
#### Unnamed Parameters
134-
135-
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.
136-
137-
```js
138-
var re = pathToRegexp('/:foo/(.*)')
139-
// keys = [{ name: 'foo', ... }, { name: 0, ... }]
144+
regexpWord.exec('/u')
145+
//=> ['/u', 'u']
140146

141-
re.exec('/test/route')
142-
//=> ['/test/route', 'test', 'route']
147+
regexpWord.exec('/users')
148+
//=> null
143149
```
144150

151+
**Tip:** Backslashes need to be escaped with another backslash in JavaScript strings.
152+
145153
### Parse
146154

147155
The parse function is exposed via `pathToRegexp.parse`. This will return an array of strings and keys.
148156

149157
```js
150-
var tokens = pathToRegexp.parse('/route/:foo/(.*)')
158+
const tokens = pathToRegexp.parse('/route/:foo/(.*)')
151159

152160
console.log(tokens[0])
153161
//=> "/route"
@@ -166,7 +174,7 @@ console.log(tokens[2])
166174
Path-To-RegExp exposes a compile function for transforming a string into a valid path.
167175

168176
```js
169-
var toPath = pathToRegexp.compile('/user/:id')
177+
const toPath = pathToRegexp.compile('/user/:id')
170178

171179
toPath({ id: 123 }) //=> "/user/123"
172180
toPath({ id: 'café' }) //=> "/user/caf%C3%A9"
@@ -175,12 +183,12 @@ toPath({ id: '/' }) //=> "/user/%2F"
175183
toPath({ id: ':/' }) //=> "/user/%3A%2F"
176184
toPath({ id: ':/' }, { encode: (value, token) => value }) //=> "/user/:/"
177185

178-
var toPathRepeated = pathToRegexp.compile('/:segment+')
186+
const toPathRepeated = pathToRegexp.compile('/:segment+')
179187

180188
toPathRepeated({ segment: 'foo' }) //=> "/foo"
181189
toPathRepeated({ segment: ['a', 'b', 'c'] }) //=> "/a/b/c"
182190

183-
var toPathRegexp = pathToRegexp.compile('/user/:id(\\d+)')
191+
const toPathRegexp = pathToRegexp.compile('/user/:id(\\d+)')
184192

185193
toPathRegexp({ id: 123 }) //=> "/user/123"
186194
toPathRegexp({ id: '123' }) //=> "/user/123"
@@ -199,11 +207,10 @@ Path-To-RegExp exposes the two functions used internally that accept an array of
199207
#### Token Information
200208

201209
* `name` The name of the token (`string` for named or `number` for index)
202-
* `prefix` The prefix character for the segment (`/` or `.`)
203-
* `delimiter` The delimiter for the segment (same as prefix or `/`)
210+
* `prefix` The prefix character for the segment (e.g. `/`)
211+
* `delimiter` The delimiter for the segment (same as prefix or default delimiter)
204212
* `optional` Indicates the token is optional (`boolean`)
205213
* `repeat` Indicates the token is repeated (`boolean`)
206-
* `partial` Indicates this token is a partial path segment (`boolean`)
207214
* `pattern` The RegExp used to match this token (`string`)
208215

209216
## Compatibility with Express <= 4.x

index.d.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ declare namespace pathToRegexp {
2626
* List of characters that can also be "end" characters.
2727
*/
2828
endsWith?: string | string[];
29+
/**
30+
* List of characters to consider delimiters when parsing. (default: `undefined`, any character)
31+
*/
32+
whitelist?: string | string[];
2933
}
3034

3135
export interface ParseOptions {
3236
/**
3337
* Set the default delimiter for repeat parameters. (default: `'/'`)
3438
*/
3539
delimiter?: string;
36-
/**
37-
* List of valid delimiter characters. (default: `'./'`)
38-
*/
39-
delimiters?: string | string[];
4040
}
4141

4242
/**
@@ -66,7 +66,6 @@ declare namespace pathToRegexp {
6666
optional: boolean;
6767
repeat: boolean;
6868
pattern: string;
69-
partial: boolean;
7069
}
7170

7271
interface PathFunctionOptions {

index.js

+21-25
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ module.exports.tokensToRegExp = tokensToRegExp
1111
* Default configs.
1212
*/
1313
var DEFAULT_DELIMITER = '/'
14-
var DEFAULT_DELIMITERS = './'
1514

1615
/**
1716
* The main path matching regexp utility.
@@ -43,7 +42,7 @@ function parse (str, options) {
4342
var index = 0
4443
var path = ''
4544
var defaultDelimiter = (options && options.delimiter) || DEFAULT_DELIMITER
46-
var delimiters = (options && options.delimiters) || DEFAULT_DELIMITERS
45+
var whitelist = (options && options.whitelist) || undefined
4746
var pathEscaped = false
4847
var res
4948

@@ -62,17 +61,18 @@ function parse (str, options) {
6261
}
6362

6463
var prev = ''
65-
var next = str[index]
6664
var name = res[2]
6765
var capture = res[3]
6866
var group = res[4]
6967
var modifier = res[5]
7068

7169
if (!pathEscaped && path.length) {
7270
var k = path.length - 1
71+
var c = path[k]
72+
var matches = whitelist ? whitelist.indexOf(c) > -1 : true
7373

74-
if (delimiters.indexOf(path[k]) > -1) {
75-
prev = path[k]
74+
if (matches) {
75+
prev = c
7676
path = path.slice(0, k)
7777
}
7878
}
@@ -84,20 +84,20 @@ function parse (str, options) {
8484
pathEscaped = false
8585
}
8686

87-
var partial = prev !== '' && next !== undefined && next !== prev
8887
var repeat = modifier === '+' || modifier === '*'
8988
var optional = modifier === '?' || modifier === '*'
90-
var delimiter = prev || defaultDelimiter
9189
var pattern = capture || group
90+
var delimiter = prev || defaultDelimiter
9291

9392
tokens.push({
9493
name: name || key++,
9594
prefix: prev,
9695
delimiter: delimiter,
9796
optional: optional,
9897
repeat: repeat,
99-
partial: partial,
100-
pattern: pattern ? escapeGroup(pattern) : '[^' + escapeString(delimiter) + ']+?'
98+
pattern: pattern
99+
? escapeGroup(pattern)
100+
: '[^' + escapeString(delimiter === defaultDelimiter ? delimiter : (delimiter + defaultDelimiter)) + ']+?'
101101
})
102102
}
103103

@@ -184,12 +184,7 @@ function tokensToFunction (tokens) {
184184
continue
185185
}
186186

187-
if (token.optional) {
188-
// Prepend partial segment prefixes.
189-
if (token.partial) path += token.prefix
190-
191-
continue
192-
}
187+
if (token.optional) continue
193188

194189
throw new TypeError('Expected "' + token.name + '" to be ' + (token.repeat ? 'an array' : 'a string'))
195190
}
@@ -249,7 +244,6 @@ function regexpToRegexp (path, keys) {
249244
delimiter: null,
250245
optional: false,
251246
repeat: false,
252-
partial: false,
253247
pattern: null
254248
})
255249
}
@@ -302,19 +296,16 @@ function tokensToRegExp (tokens, keys, options) {
302296
var strict = options.strict
303297
var start = options.start !== false
304298
var end = options.end !== false
305-
var delimiter = escapeString(options.delimiter || DEFAULT_DELIMITER)
306-
var delimiters = options.delimiters || DEFAULT_DELIMITERS
299+
var delimiter = options.delimiter || DEFAULT_DELIMITER
307300
var endsWith = [].concat(options.endsWith || []).map(escapeString).concat('$').join('|')
308301
var route = start ? '^' : ''
309-
var isEndDelimited = tokens.length === 0
310302

311303
// Iterate over the tokens and create our regexp string.
312304
for (var i = 0; i < tokens.length; i++) {
313305
var token = tokens[i]
314306

315307
if (typeof token === 'string') {
316308
route += escapeString(token)
317-
isEndDelimited = i === tokens.length - 1 && delimiters.indexOf(token[token.length - 1]) > -1
318309
} else {
319310
var capture = token.repeat
320311
? '(?:' + token.pattern + ')(?:' + escapeString(token.delimiter) + '(?:' + token.pattern + '))*'
@@ -323,8 +314,8 @@ function tokensToRegExp (tokens, keys, options) {
323314
if (keys) keys.push(token)
324315

325316
if (token.optional) {
326-
if (token.partial) {
327-
route += escapeString(token.prefix) + '(' + capture + ')?'
317+
if (!token.prefix) {
318+
route += '(' + capture + ')?'
328319
} else {
329320
route += '(?:' + escapeString(token.prefix) + '(' + capture + '))?'
330321
}
@@ -335,12 +326,17 @@ function tokensToRegExp (tokens, keys, options) {
335326
}
336327

337328
if (end) {
338-
if (!strict) route += '(?:' + delimiter + ')?'
329+
if (!strict) route += '(?:' + escapeString(delimiter) + ')?'
339330

340331
route += endsWith === '$' ? '$' : '(?=' + endsWith + ')'
341332
} else {
342-
if (!strict) route += '(?:' + delimiter + '(?=' + endsWith + '))?'
343-
if (!isEndDelimited) route += '(?=' + delimiter + '|' + endsWith + ')'
333+
var endToken = tokens[tokens.length - 1]
334+
var isEndDelimited = typeof endToken === 'string'
335+
? endToken[endToken.length - 1] === delimiter
336+
: endToken === undefined
337+
338+
if (!strict) route += '(?:' + escapeString(delimiter) + '(?=' + endsWith + '))?'
339+
if (!isEndDelimited) route += '(?=' + escapeString(delimiter) + '|' + endsWith + ')'
344340
}
345341

346342
return new RegExp(route, flags(options))

0 commit comments

Comments
 (0)