path-to-regexp 1.8.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/History.md +23 -0
  2. package/Readme.md +33 -50
  3. package/index.d.ts +14 -18
  4. package/index.js +103 -158
  5. package/package.json +10 -12
package/History.md CHANGED
@@ -1,3 +1,26 @@
1
+ 2.2.0 / 2018-03-06
2
+ ==================
3
+
4
+ * Pass `token` as second argument to `encode` option (e.g. `encode(value, token)`)
5
+
6
+ 2.1.0 / 2017-10-20
7
+ ==================
8
+
9
+ * Handle non-ending paths where the final character is a delimiter
10
+ * E.g. `/foo/` before required either `/foo/` or `/foo//` to match in non-ending mode
11
+
12
+ 2.0.0 / 2017-08-23
13
+ ==================
14
+
15
+ * New option! Ability to set `endsWith` to match paths like `/test?query=string` up to the query string
16
+ * New option! Set `delimiters` for specific characters to be treated as parameter prefixes (e.g. `/:test`)
17
+ * Remove `isarray` dependency
18
+ * Explicitly handle trailing delimiters instead of trimming them (e.g. `/test/` is now treated as `/test/` instead of `/test` when matching)
19
+ * Remove overloaded `keys` argument that accepted `options`
20
+ * Remove `keys` list attached to the `RegExp` output
21
+ * Remove asterisk functionality (it's a real pain to properly encode)
22
+ * Change `tokensToFunction` (e.g. `compile`) to accept an `encode` function for pretty encoding (e.g. pass your own implementation)
23
+
1
24
  1.7.0 / 2016-11-08
2
25
  ==================
3
26
 
package/Readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Path-to-RegExp
2
2
 
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.
4
4
 
5
5
  [![NPM version][npm-image]][npm-url]
6
6
  [![Build status][travis-image]][travis-url]
@@ -20,18 +20,21 @@ npm install path-to-regexp --save
20
20
  ```javascript
21
21
  var pathToRegexp = require('path-to-regexp')
22
22
 
23
- // pathToRegexp(path, keys, options)
23
+ // pathToRegexp(path, keys?, options?)
24
24
  // pathToRegexp.parse(path)
25
25
  // pathToRegexp.compile(path)
26
26
  ```
27
27
 
28
- - **path** An Express-style string, an array of strings, or a regular expression.
28
+ - **path** A string, array of strings, or a regular expression.
29
29
  - **keys** An array to be populated with the keys found in the path.
30
30
  - **options**
31
31
  - **sensitive** When `true` the route will be case sensitive. (default: `false`)
32
32
  - **strict** When `false` the trailing slash is optional. (default: `false`)
33
33
  - **end** When `false` the path will match at the beginning. (default: `true`)
34
- - **delimiter** Set the default delimiter for repeat parameters. (default: `'/'`)
34
+ - Advanced options (use for non-pathname strings, e.g. host names):
35
+ - **delimiter** The default delimiter for segments. (default: `'/'`)
36
+ - **endsWith** Optional character, or list of characters, to treat as "end" characters.
37
+ - **delimiters** List of characters to consider delimiters when parsing. (default: `'./'`)
35
38
 
36
39
  ```javascript
37
40
  var keys = []
@@ -40,42 +43,34 @@ var re = pathToRegexp('/foo/:bar', keys)
40
43
  // keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
41
44
  ```
42
45
 
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.
46
+ **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).
44
47
 
45
48
  ### Parameters
46
49
 
47
- The path string can be used to define parameters and populate the keys.
50
+ The path argument is used to define parameters and populate the list of keys.
48
51
 
49
52
  #### Named Parameters
50
53
 
51
54
  Named parameters are defined by prefixing a colon to the parameter name (`:foo`). By default, the parameter will match until the following path segment.
52
55
 
53
56
  ```js
54
- var re = pathToRegexp('/:foo/:bar', keys)
57
+ var re = pathToRegexp('/:foo/:bar')
55
58
  // keys = [{ name: 'foo', prefix: '/', ... }, { name: 'bar', prefix: '/', ... }]
56
59
 
57
60
  re.exec('/test/route')
58
61
  //=> ['/test/route', 'test', 'route']
59
62
  ```
60
63
 
61
- **Please note:** Named parameters must be made up of "word characters" (`[A-Za-z0-9_]`).
64
+ **Please note:** Parameter names must be made up of "word characters" (`[A-Za-z0-9_]`).
62
65
 
63
- ```js
64
- var re = pathToRegexp('/(apple-)?icon-:res(\\d+).png', keys)
65
- // keys = [{ name: 0, prefix: '/', ... }, { name: 'res', prefix: '', ... }]
66
-
67
- re.exec('/icon-76.png')
68
- //=> ['/icon-76.png', undefined, '76']
69
- ```
70
-
71
- #### Modified Parameters
66
+ #### Parameter Modifiers
72
67
 
73
68
  ##### Optional
74
69
 
75
- Parameters can be suffixed with a question mark (`?`) to make the parameter optional. This will also make the prefix optional.
70
+ Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
76
71
 
77
72
  ```js
78
- var re = pathToRegexp('/:foo/:bar?', keys)
73
+ var re = pathToRegexp('/:foo/:bar?')
79
74
  // keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }]
80
75
 
81
76
  re.exec('/test')
@@ -85,12 +80,14 @@ re.exec('/test/route')
85
80
  //=> ['/test', 'test', 'route']
86
81
  ```
87
82
 
83
+ **Tip:** If the parameter is the _only_ value in the segment, the prefix is also optional.
84
+
88
85
  ##### Zero or more
89
86
 
90
87
  Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches. The prefix is taken into account for each match.
91
88
 
92
89
  ```js
93
- var re = pathToRegexp('/:foo*', keys)
90
+ var re = pathToRegexp('/:foo*')
94
91
  // keys = [{ name: 'foo', delimiter: '/', optional: true, repeat: true }]
95
92
 
96
93
  re.exec('/')
@@ -105,7 +102,7 @@ re.exec('/bar/baz')
105
102
  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.
106
103
 
107
104
  ```js
108
- var re = pathToRegexp('/:foo+', keys)
105
+ var re = pathToRegexp('/:foo+')
109
106
  // keys = [{ name: 'foo', delimiter: '/', optional: false, repeat: true }]
110
107
 
111
108
  re.exec('/')
@@ -115,18 +112,18 @@ re.exec('/bar/baz')
115
112
  //=> ['/bar/baz', 'bar/baz']
116
113
  ```
117
114
 
118
- #### Custom Match Parameters
115
+ #### Custom Matching Parameters
119
116
 
120
- All parameters can be provided a custom regexp, which overrides the default (`[^\/]+`).
117
+ All parameters can be provided a custom regexp, which overrides the default match (`[^\/]+`). For example, you can match digits in the path:
121
118
 
122
119
  ```js
123
- var re = pathToRegexp('/:foo(\\d+)', keys)
120
+ var re = pathToRegexp('/icon-:foo(\\d+).png')
124
121
  // keys = [{ name: 'foo', ... }]
125
122
 
126
- re.exec('/123')
127
- //=> ['/123', '123']
123
+ re.exec('/icon-123.png')
124
+ //=> ['/icon-123.png', '123']
128
125
 
129
- re.exec('/abc')
126
+ re.exec('/icon-abc.png')
130
127
  //=> null
131
128
  ```
132
129
 
@@ -137,25 +134,13 @@ re.exec('/abc')
137
134
  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.
138
135
 
139
136
  ```js
140
- var re = pathToRegexp('/:foo/(.*)', keys)
137
+ var re = pathToRegexp('/:foo/(.*)')
141
138
  // keys = [{ name: 'foo', ... }, { name: 0, ... }]
142
139
 
143
140
  re.exec('/test/route')
144
141
  //=> ['/test/route', 'test', 'route']
145
142
  ```
146
143
 
147
- #### Asterisk
148
-
149
- An asterisk can be used for matching everything. It is equivalent to an unnamed matching group of `(.*)`.
150
-
151
- ```js
152
- var re = pathToRegexp('/foo/*', keys)
153
- // keys = [{ name: '0', ... }]
154
-
155
- re.exec('/foo/bar/baz')
156
- //=> ['/foo/bar/baz', 'bar/baz']
157
- ```
158
-
159
144
  ### Parse
160
145
 
161
146
  The parse function is exposed via `pathToRegexp.parse`. This will return an array of strings and keys.
@@ -173,11 +158,11 @@ console.log(tokens[2])
173
158
  //=> { name: 0, prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '.*' }
174
159
  ```
175
160
 
176
- **Note:** This method only works with Express-style strings.
161
+ **Note:** This method only works with strings.
177
162
 
178
163
  ### Compile ("Reverse" Path-To-RegExp)
179
164
 
180
- Path-To-RegExp exposes a compile function for transforming an Express-style path into a valid path.
165
+ Path-To-RegExp exposes a compile function for transforming a string into a valid path.
181
166
 
182
167
  ```js
183
168
  var toPath = pathToRegexp.compile('/user/:id')
@@ -186,8 +171,8 @@ toPath({ id: 123 }) //=> "/user/123"
186
171
  toPath({ id: 'café' }) //=> "/user/caf%C3%A9"
187
172
  toPath({ id: '/' }) //=> "/user/%2F"
188
173
 
189
- toPath({ id: ':' }) //=> "/user/%3A"
190
- toPath({ id: ':' }, { pretty: true }) //=> "/user/:"
174
+ toPath({ id: ':/' }) //=> "/user/%3A%2F"
175
+ toPath({ id: ':/' }, { encode: (value, token) => value }) //=> "/user/:/"
191
176
 
192
177
  var toPathRepeated = pathToRegexp.compile('/:segment+')
193
178
 
@@ -207,7 +192,7 @@ toPathRegexp({ id: 'abc' }) //=> Throws `TypeError`.
207
192
 
208
193
  Path-To-RegExp exposes the two functions used internally that accept an array of tokens.
209
194
 
210
- * `pathToRegexp.tokensToRegExp(tokens, options)` Transform an array of tokens into a matching regular expression.
195
+ * `pathToRegexp.tokensToRegExp(tokens, keys?, options?)` Transform an array of tokens into a matching regular expression.
211
196
  * `pathToRegexp.tokensToFunction(tokens)` Transform an array of tokens into a path generator function.
212
197
 
213
198
  #### Token Information
@@ -219,17 +204,15 @@ Path-To-RegExp exposes the two functions used internally that accept an array of
219
204
  * `repeat` Indicates the token is repeated (`boolean`)
220
205
  * `partial` Indicates this token is a partial path segment (`boolean`)
221
206
  * `pattern` The RegExp used to match this token (`string`)
222
- * `asterisk` Indicates the token is an `*` match (`boolean`)
223
207
 
224
208
  ## Compatibility with Express <= 4.x
225
209
 
226
210
  Path-To-RegExp breaks compatibility with Express <= `4.x`:
227
211
 
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
212
+ * RegExp special characters can only be used in a parameter
213
+ * Express.js 4.x used all `RegExp` special characters regardless of position - this considered a bug
232
214
  * Parameters have suffixes that augment meaning - `*`, `+` and `?`. E.g. `/:user*`
215
+ * No wildcard asterisk (`*`) - use parameters instead (`(.*)`)
233
216
 
234
217
  ## TypeScript
235
218
 
package/index.d.ts CHANGED
@@ -1,12 +1,6 @@
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, keys?: pathToRegexp.Key[], options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions): RegExp;
3
2
 
4
3
  declare namespace pathToRegexp {
5
- export interface PathRegExp extends RegExp {
6
- // An array to be populated with the keys found in the path.
7
- keys: Key[];
8
- }
9
-
10
4
  export interface RegExpOptions {
11
5
  /**
12
6
  * When `true` the route will be case sensitive. (default: `false`)
@@ -24,6 +18,10 @@ declare namespace pathToRegexp {
24
18
  * Sets the final character for non-ending optimistic matches. (default: `/`)
25
19
  */
26
20
  delimiter?: string;
21
+ /**
22
+ * List of characters that can also be "end" characters.
23
+ */
24
+ endsWith?: string | string[];
27
25
  }
28
26
 
29
27
  export interface ParseOptions {
@@ -31,13 +29,10 @@ declare namespace pathToRegexp {
31
29
  * Set the default delimiter for repeat parameters. (default: `'/'`)
32
30
  */
33
31
  delimiter?: string;
34
- }
35
-
36
- export interface TokensToFunctionOptions {
37
32
  /**
38
- * When `true` the regexp will be case sensitive. (default: `false`)
33
+ * List of valid delimiter characters. (default: `'./'`)
39
34
  */
40
- sensitive?: boolean;
35
+ delimiters?: string | string[];
41
36
  }
42
37
 
43
38
  /**
@@ -48,18 +43,17 @@ declare namespace pathToRegexp {
48
43
  /**
49
44
  * Transforming an Express-style path into a valid path.
50
45
  */
51
- export function compile (path: string, options?: ParseOptions & TokensToFunctionOptions): PathFunction;
46
+ export function compile (path: string, options?: ParseOptions): PathFunction;
52
47
 
53
48
  /**
54
49
  * Transform an array of tokens into a path generator function.
55
50
  */
56
- export function tokensToFunction (tokens: Token[], options?: TokensToFunctionOptions): PathFunction;
51
+ export function tokensToFunction (tokens: Token[]): PathFunction;
57
52
 
58
53
  /**
59
54
  * Transform an array of tokens into a matching regular expression.
60
55
  */
61
- export function tokensToRegExp (tokens: Token[], options?: RegExpOptions): PathRegExp;
62
- export function tokensToRegExp (tokens: Token[], keys?: Key[], options?: RegExpOptions): PathRegExp;
56
+ export function tokensToRegExp (tokens: Token[], keys?: Key[], options?: RegExpOptions): RegExp;
63
57
 
64
58
  export interface Key {
65
59
  name: string | number;
@@ -69,11 +63,13 @@ declare namespace pathToRegexp {
69
63
  repeat: boolean;
70
64
  pattern: string;
71
65
  partial: boolean;
72
- asterisk: boolean;
73
66
  }
74
67
 
75
68
  interface PathFunctionOptions {
76
- pretty?: boolean;
69
+ /**
70
+ * Function for encoding input strings for output.
71
+ */
72
+ encode?: (value: string, token: Key) => string;
77
73
  }
78
74
 
79
75
  export type Token = string | Key;
package/index.js CHANGED
@@ -1,5 +1,3 @@
1
- var isarray = require('isarray')
2
-
3
1
  /**
4
2
  * Expose `pathToRegexp`.
5
3
  */
@@ -9,6 +7,12 @@ module.exports.compile = compile
9
7
  module.exports.tokensToFunction = tokensToFunction
10
8
  module.exports.tokensToRegExp = tokensToRegExp
11
9
 
10
+ /**
11
+ * Default configs.
12
+ */
13
+ var DEFAULT_DELIMITER = '/'
14
+ var DEFAULT_DELIMITERS = './'
15
+
12
16
  /**
13
17
  * The main path matching regexp utility.
14
18
  *
@@ -21,10 +25,9 @@ var PATH_REGEXP = new RegExp([
21
25
  // Match Express-style parameters and un-named parameters with a prefix
22
26
  // and optional suffixes. Matches appear as:
23
27
  //
24
- // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
25
- // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
26
- // "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
27
- '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'
28
+ // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"]
29
+ // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
30
+ '(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?'
28
31
  ].join('|'), 'g')
29
32
 
30
33
  /**
@@ -39,10 +42,12 @@ function parse (str, options) {
39
42
  var key = 0
40
43
  var index = 0
41
44
  var path = ''
42
- var defaultDelimiter = options && options.delimiter || '/'
45
+ var defaultDelimiter = (options && options.delimiter) || DEFAULT_DELIMITER
46
+ var delimiters = (options && options.delimiters) || DEFAULT_DELIMITERS
47
+ var pathEscaped = false
43
48
  var res
44
49
 
45
- while ((res = PATH_REGEXP.exec(str)) != null) {
50
+ while ((res = PATH_REGEXP.exec(str)) !== null) {
46
51
  var m = res[0]
47
52
  var escaped = res[1]
48
53
  var offset = res.index
@@ -52,49 +57,53 @@ function parse (str, options) {
52
57
  // Ignore already escaped sequences.
53
58
  if (escaped) {
54
59
  path += escaped[1]
60
+ pathEscaped = true
55
61
  continue
56
62
  }
57
63
 
64
+ var prev = ''
58
65
  var next = str[index]
59
- var prefix = res[2]
60
- var name = res[3]
61
- var capture = res[4]
62
- var group = res[5]
63
- var modifier = res[6]
64
- var asterisk = res[7]
66
+ var name = res[2]
67
+ var capture = res[3]
68
+ var group = res[4]
69
+ var modifier = res[5]
70
+
71
+ if (!pathEscaped && path.length) {
72
+ var k = path.length - 1
73
+
74
+ if (delimiters.indexOf(path[k]) > -1) {
75
+ prev = path[k]
76
+ path = path.slice(0, k)
77
+ }
78
+ }
65
79
 
66
80
  // Push the current path onto the tokens.
67
81
  if (path) {
68
82
  tokens.push(path)
69
83
  path = ''
84
+ pathEscaped = false
70
85
  }
71
86
 
72
- var partial = prefix != null && next != null && next !== prefix
87
+ var partial = prev !== '' && next !== undefined && next !== prev
73
88
  var repeat = modifier === '+' || modifier === '*'
74
89
  var optional = modifier === '?' || modifier === '*'
75
- var delimiter = res[2] || defaultDelimiter
90
+ var delimiter = prev || defaultDelimiter
76
91
  var pattern = capture || group
77
92
 
78
93
  tokens.push({
79
94
  name: name || key++,
80
- prefix: prefix || '',
95
+ prefix: prev,
81
96
  delimiter: delimiter,
82
97
  optional: optional,
83
98
  repeat: repeat,
84
99
  partial: partial,
85
- asterisk: !!asterisk,
86
- pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
100
+ pattern: pattern ? escapeGroup(pattern) : '[^' + escapeString(delimiter) + ']+?'
87
101
  })
88
102
  }
89
103
 
90
- // Match any characters still remaining.
91
- if (index < str.length) {
92
- path += str.substr(index)
93
- }
94
-
95
- // If the path exists, push it onto the end.
96
- if (path) {
97
- tokens.push(path)
104
+ // Push any remaining characters.
105
+ if (path || index < str.length) {
106
+ tokens.push(path + str.substr(index))
98
107
  }
99
108
 
100
109
  return tokens
@@ -108,96 +117,54 @@ function parse (str, options) {
108
117
  * @return {!function(Object=, Object=)}
109
118
  */
110
119
  function compile (str, options) {
111
- return tokensToFunction(parse(str, options), options)
112
- }
113
-
114
- /**
115
- * Prettier encoding of URI path segments.
116
- *
117
- * @param {string}
118
- * @return {string}
119
- */
120
- function encodeURIComponentPretty (str) {
121
- return encodeURI(str).replace(/[\/?#]/g, function (c) {
122
- return '%' + c.charCodeAt(0).toString(16).toUpperCase()
123
- })
124
- }
125
-
126
- /**
127
- * Encode the asterisk parameter. Similar to `pretty`, but allows slashes.
128
- *
129
- * @param {string}
130
- * @return {string}
131
- */
132
- function encodeAsterisk (str) {
133
- return encodeURI(str).replace(/[?#]/g, function (c) {
134
- return '%' + c.charCodeAt(0).toString(16).toUpperCase()
135
- })
120
+ return tokensToFunction(parse(str, options))
136
121
  }
137
122
 
138
123
  /**
139
124
  * Expose a method for transforming tokens into the path function.
140
125
  */
141
- function tokensToFunction (tokens, options) {
126
+ function tokensToFunction (tokens) {
142
127
  // Compile all the tokens into regexps.
143
128
  var matches = new Array(tokens.length)
144
129
 
145
130
  // Compile all the patterns before compilation.
146
131
  for (var i = 0; i < tokens.length; i++) {
147
132
  if (typeof tokens[i] === 'object') {
148
- matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options))
133
+ matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$')
149
134
  }
150
135
  }
151
136
 
152
- return function (obj, opts) {
137
+ return function (data, options) {
153
138
  var path = ''
154
- var data = obj || {}
155
- var options = opts || {}
156
- var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent
139
+ var encode = (options && options.encode) || encodeURIComponent
157
140
 
158
141
  for (var i = 0; i < tokens.length; i++) {
159
142
  var token = tokens[i]
160
143
 
161
144
  if (typeof token === 'string') {
162
145
  path += token
163
-
164
146
  continue
165
147
  }
166
148
 
167
- var value = data[token.name]
149
+ var value = data ? data[token.name] : undefined
168
150
  var segment
169
151
 
170
- if (value == null) {
171
- if (token.optional) {
172
- // Prepend partial segment prefixes.
173
- if (token.partial) {
174
- path += token.prefix
175
- }
176
-
177
- continue
178
- } else {
179
- throw new TypeError('Expected "' + token.name + '" to be defined')
180
- }
181
- }
182
-
183
- if (isarray(value)) {
152
+ if (Array.isArray(value)) {
184
153
  if (!token.repeat) {
185
- throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
154
+ throw new TypeError('Expected "' + token.name + '" to not repeat, but got array')
186
155
  }
187
156
 
188
157
  if (value.length === 0) {
189
- if (token.optional) {
190
- continue
191
- } else {
192
- throw new TypeError('Expected "' + token.name + '" to not be empty')
193
- }
158
+ if (token.optional) continue
159
+
160
+ throw new TypeError('Expected "' + token.name + '" to not be empty')
194
161
  }
195
162
 
196
163
  for (var j = 0; j < value.length; j++) {
197
- segment = encode(value[j])
164
+ segment = encode(value[j], token)
198
165
 
199
166
  if (!matches[i].test(segment)) {
200
- throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
167
+ throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '"')
201
168
  }
202
169
 
203
170
  path += (j === 0 ? token.prefix : token.delimiter) + segment
@@ -206,13 +173,25 @@ function tokensToFunction (tokens, options) {
206
173
  continue
207
174
  }
208
175
 
209
- segment = token.asterisk ? encodeAsterisk(value) : encode(value)
176
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
177
+ segment = encode(String(value), token)
210
178
 
211
- if (!matches[i].test(segment)) {
212
- throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
179
+ if (!matches[i].test(segment)) {
180
+ throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but got "' + segment + '"')
181
+ }
182
+
183
+ path += token.prefix + segment
184
+ continue
185
+ }
186
+
187
+ if (token.optional) {
188
+ // Prepend partial segment prefixes.
189
+ if (token.partial) path += token.prefix
190
+
191
+ continue
213
192
  }
214
193
 
215
- path += token.prefix + segment
194
+ throw new TypeError('Expected "' + token.name + '" to be ' + (token.repeat ? 'an array' : 'a string'))
216
195
  }
217
196
 
218
197
  return path
@@ -226,7 +205,7 @@ function tokensToFunction (tokens, options) {
226
205
  * @return {string}
227
206
  */
228
207
  function escapeString (str) {
229
- return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
208
+ return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1')
230
209
  }
231
210
 
232
211
  /**
@@ -236,19 +215,7 @@ function escapeString (str) {
236
215
  * @return {string}
237
216
  */
238
217
  function escapeGroup (group) {
239
- return group.replace(/([=!:$\/()])/g, '\\$1')
240
- }
241
-
242
- /**
243
- * Attach the keys as a property of the regexp.
244
- *
245
- * @param {!RegExp} re
246
- * @param {Array} keys
247
- * @return {!RegExp}
248
- */
249
- function attachKeys (re, keys) {
250
- re.keys = keys
251
- return re
218
+ return group.replace(/([=!:$/()])/g, '\\$1')
252
219
  }
253
220
 
254
221
  /**
@@ -265,10 +232,12 @@ function flags (options) {
265
232
  * Pull out keys from a regexp.
266
233
  *
267
234
  * @param {!RegExp} path
268
- * @param {!Array} keys
235
+ * @param {Array=} keys
269
236
  * @return {!RegExp}
270
237
  */
271
238
  function regexpToRegexp (path, keys) {
239
+ if (!keys) return path
240
+
272
241
  // Use a negative lookahead to match only capturing groups.
273
242
  var groups = path.source.match(/\((?!\?)/g)
274
243
 
@@ -281,21 +250,20 @@ function regexpToRegexp (path, keys) {
281
250
  optional: false,
282
251
  repeat: false,
283
252
  partial: false,
284
- asterisk: false,
285
253
  pattern: null
286
254
  })
287
255
  }
288
256
  }
289
257
 
290
- return attachKeys(path, keys)
258
+ return path
291
259
  }
292
260
 
293
261
  /**
294
262
  * Transform an array into a regexp.
295
263
  *
296
264
  * @param {!Array} path
297
- * @param {Array} keys
298
- * @param {!Object} options
265
+ * @param {Array=} keys
266
+ * @param {Object=} options
299
267
  * @return {!RegExp}
300
268
  */
301
269
  function arrayToRegexp (path, keys, options) {
@@ -305,17 +273,15 @@ function arrayToRegexp (path, keys, options) {
305
273
  parts.push(pathToRegexp(path[i], keys, options).source)
306
274
  }
307
275
 
308
- var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options))
309
-
310
- return attachKeys(regexp, keys)
276
+ return new RegExp('(?:' + parts.join('|') + ')', flags(options))
311
277
  }
312
278
 
313
279
  /**
314
280
  * Create a path regexp from string input.
315
281
  *
316
282
  * @param {string} path
317
- * @param {!Array} keys
318
- * @param {!Object} options
283
+ * @param {Array=} keys
284
+ * @param {Object=} options
319
285
  * @return {!RegExp}
320
286
  */
321
287
  function stringToRegexp (path, keys, options) {
@@ -325,22 +291,21 @@ function stringToRegexp (path, keys, options) {
325
291
  /**
326
292
  * Expose a function for taking tokens and returning a RegExp.
327
293
  *
328
- * @param {!Array} tokens
329
- * @param {(Array|Object)=} keys
330
- * @param {Object=} options
294
+ * @param {!Array} tokens
295
+ * @param {Array=} keys
296
+ * @param {Object=} options
331
297
  * @return {!RegExp}
332
298
  */
333
299
  function tokensToRegExp (tokens, keys, options) {
334
- if (!isarray(keys)) {
335
- options = /** @type {!Object} */ (keys || options)
336
- keys = []
337
- }
338
-
339
300
  options = options || {}
340
301
 
341
302
  var strict = options.strict
342
303
  var end = options.end !== false
304
+ var delimiter = escapeString(options.delimiter || DEFAULT_DELIMITER)
305
+ var delimiters = options.delimiters || DEFAULT_DELIMITERS
306
+ var endsWith = [].concat(options.endsWith || []).map(escapeString).concat('$').join('|')
343
307
  var route = ''
308
+ var isEndDelimited = tokens.length === 0
344
309
 
345
310
  // Iterate over the tokens and create our regexp string.
346
311
  for (var i = 0; i < tokens.length; i++) {
@@ -348,50 +313,37 @@ function tokensToRegExp (tokens, keys, options) {
348
313
 
349
314
  if (typeof token === 'string') {
350
315
  route += escapeString(token)
316
+ isEndDelimited = i === tokens.length - 1 && delimiters.indexOf(token[token.length - 1]) > -1
351
317
  } else {
352
318
  var prefix = escapeString(token.prefix)
353
- var capture = '(?:' + token.pattern + ')'
319
+ var capture = token.repeat
320
+ ? '(?:' + token.pattern + ')(?:' + prefix + '(?:' + token.pattern + '))*'
321
+ : token.pattern
354
322
 
355
- keys.push(token)
356
-
357
- if (token.repeat) {
358
- capture += '(?:' + prefix + capture + ')*'
359
- }
323
+ if (keys) keys.push(token)
360
324
 
361
325
  if (token.optional) {
362
- if (!token.partial) {
363
- capture = '(?:' + prefix + '(' + capture + '))?'
326
+ if (token.partial) {
327
+ route += prefix + '(' + capture + ')?'
364
328
  } else {
365
- capture = prefix + '(' + capture + ')?'
329
+ route += '(?:' + prefix + '(' + capture + '))?'
366
330
  }
367
331
  } else {
368
- capture = prefix + '(' + capture + ')'
332
+ route += prefix + '(' + capture + ')'
369
333
  }
370
-
371
- route += capture
372
334
  }
373
335
  }
374
336
 
375
- var delimiter = escapeString(options.delimiter || '/')
376
- var endsWithDelimiter = route.slice(-delimiter.length) === delimiter
377
-
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".
382
- if (!strict) {
383
- route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'
384
- }
385
-
386
337
  if (end) {
387
- route += '$'
338
+ if (!strict) route += '(?:' + delimiter + ')?'
339
+
340
+ route += endsWith === '$' ? '$' : '(?=' + endsWith + ')'
388
341
  } else {
389
- // In non-ending mode, we need the capturing groups to match as much as
390
- // possible by using a positive lookahead to the end or next path segment.
391
- route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'
342
+ if (!strict) route += '(?:' + delimiter + '(?=' + endsWith + '))?'
343
+ if (!isEndDelimited) route += '(?=' + delimiter + '|' + endsWith + ')'
392
344
  }
393
345
 
394
- return attachKeys(new RegExp('^' + route, flags(options)), keys)
346
+ return new RegExp('^' + route, flags(options))
395
347
  }
396
348
 
397
349
  /**
@@ -402,25 +354,18 @@ function tokensToRegExp (tokens, keys, options) {
402
354
  * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
403
355
  *
404
356
  * @param {(string|RegExp|Array)} path
405
- * @param {(Array|Object)=} keys
357
+ * @param {Array=} keys
406
358
  * @param {Object=} options
407
359
  * @return {!RegExp}
408
360
  */
409
361
  function pathToRegexp (path, keys, options) {
410
- if (!isarray(keys)) {
411
- options = /** @type {!Object} */ (keys || options)
412
- keys = []
413
- }
414
-
415
- options = options || {}
416
-
417
362
  if (path instanceof RegExp) {
418
- return regexpToRegexp(path, /** @type {!Array} */ (keys))
363
+ return regexpToRegexp(path, keys)
419
364
  }
420
365
 
421
- if (isarray(path)) {
422
- return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
366
+ if (Array.isArray(path)) {
367
+ return arrayToRegexp(/** @type {!Array} */ (path), keys, options)
423
368
  }
424
369
 
425
- return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
370
+ return stringToRegexp(/** @type {string} */ (path), keys, options)
426
371
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "path-to-regexp",
3
3
  "description": "Express style path to RegExp utility",
4
- "version": "1.8.0",
4
+ "version": "2.2.1",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
7
7
  "files": [
@@ -13,7 +13,6 @@
13
13
  "lint": "standard",
14
14
  "test-spec": "mocha --require ts-node/register -R spec --bail test.ts",
15
15
  "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require ts-node/register -R spec test.ts",
16
- "prepublish": "typings install",
17
16
  "test": "npm run lint && npm run test-cov"
18
17
  },
19
18
  "keywords": [
@@ -33,15 +32,14 @@
33
32
  "url": "https://github.com/pillarjs/path-to-regexp.git"
34
33
  },
35
34
  "devDependencies": {
36
- "chai": "^2.3.0",
37
- "istanbul": "~0.3.0",
38
- "mocha": "~2.2.4",
39
- "standard": "~3.7.3",
40
- "ts-node": "^0.5.5",
41
- "typescript": "^1.8.7",
42
- "typings": "^1.0.4"
43
- },
44
- "dependencies": {
45
- "isarray": "0.0.1"
35
+ "@types/chai": "^4.0.4",
36
+ "@types/mocha": "^2.2.42",
37
+ "@types/node": "^8.0.24",
38
+ "chai": "^4.1.1",
39
+ "istanbul": "^0.4.5",
40
+ "mocha": "^3.5.0",
41
+ "standard": "^10.0.3",
42
+ "ts-node": "^3.3.0",
43
+ "typescript": "^2.4.2"
46
44
  }
47
45
  }