path-to-regexp 1.6.0 → 2.1.0

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 +24 -0
  2. package/Readme.md +27 -36
  3. package/index.d.ts +18 -11
  4. package/index.js +100 -154
  5. package/package.json +10 -12
package/History.md CHANGED
@@ -1,3 +1,27 @@
1
+ 2.0.0 / 2017-08-23
2
+ ==================
3
+
4
+ * New option! Ability to set `endsWith` to match paths like `/test?query=string` up to the query string
5
+ * New option! Set `delimiters` for specific characters to be treated as parameter prefixes (e.g. `/:test`)
6
+ * Remove `isarray` dependency
7
+ * Explicitly handle trailing delimiters instead of trimming them (e.g. `/test/` is now treated as `/test/` instead of `/test` when matching)
8
+ * Remove overloaded `keys` argument that accepted `options`
9
+ * Remove `keys` list attached to the `RegExp` output
10
+ * Remove asterisk functionality (it's a real pain to properly encode)
11
+ * Change `tokensToFunction` (e.g. `compile`) to accept an `encode` function for pretty encoding (e.g. pass your own implementation)
12
+
13
+ 1.7.0 / 2016-11-08
14
+ ==================
15
+
16
+ * Allow a `delimiter` option to be passed in with `tokensToRegExp` which will be used for "non-ending" token match situations
17
+
18
+ 1.6.0 / 2016-10-03
19
+ ==================
20
+
21
+ * Populate `RegExp.keys` when using the `tokensToRegExp` method (making it consistent with the main export)
22
+ * Allow a `delimiter` option to be passed in with `parse`
23
+ * Updated TypeScript definition with `Keys` and `Options` updated
24
+
1
25
  1.5.3 / 2016-06-15
2
26
  ==================
3
27
 
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,18 +43,18 @@ 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')
@@ -61,21 +64,21 @@ re.exec('/test/route')
61
64
  **Please note:** Named parameters must be made up of "word characters" (`[A-Za-z0-9_]`).
62
65
 
63
66
  ```js
64
- var re = pathToRegexp('/(apple-)?icon-:res(\\d+).png', keys)
67
+ var re = pathToRegexp('/(apple-)?icon-:res(\\d+).png')
65
68
  // keys = [{ name: 0, prefix: '/', ... }, { name: 'res', prefix: '', ... }]
66
69
 
67
70
  re.exec('/icon-76.png')
68
71
  //=> ['/icon-76.png', undefined, '76']
69
72
  ```
70
73
 
71
- #### Modified Parameters
74
+ #### Parameter Modifiers
72
75
 
73
76
  ##### Optional
74
77
 
75
- Parameters can be suffixed with a question mark (`?`) to make the parameter optional. This will also make the prefix optional.
78
+ Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
76
79
 
77
80
  ```js
78
- var re = pathToRegexp('/:foo/:bar?', keys)
81
+ var re = pathToRegexp('/:foo/:bar?')
79
82
  // keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }]
80
83
 
81
84
  re.exec('/test')
@@ -85,12 +88,14 @@ re.exec('/test/route')
85
88
  //=> ['/test', 'test', 'route']
86
89
  ```
87
90
 
91
+ **Tip:** If the parameter is the _only_ value in the segment, the prefix is also optional.
92
+
88
93
  ##### Zero or more
89
94
 
90
95
  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
96
 
92
97
  ```js
93
- var re = pathToRegexp('/:foo*', keys)
98
+ var re = pathToRegexp('/:foo*')
94
99
  // keys = [{ name: 'foo', delimiter: '/', optional: true, repeat: true }]
95
100
 
96
101
  re.exec('/')
@@ -105,7 +110,7 @@ re.exec('/bar/baz')
105
110
  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
111
 
107
112
  ```js
108
- var re = pathToRegexp('/:foo+', keys)
113
+ var re = pathToRegexp('/:foo+')
109
114
  // keys = [{ name: 'foo', delimiter: '/', optional: false, repeat: true }]
110
115
 
111
116
  re.exec('/')
@@ -120,7 +125,7 @@ re.exec('/bar/baz')
120
125
  All parameters can be provided a custom regexp, which overrides the default (`[^\/]+`).
121
126
 
122
127
  ```js
123
- var re = pathToRegexp('/:foo(\\d+)', keys)
128
+ var re = pathToRegexp('/:foo(\\d+)')
124
129
  // keys = [{ name: 'foo', ... }]
125
130
 
126
131
  re.exec('/123')
@@ -137,25 +142,13 @@ re.exec('/abc')
137
142
  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
143
 
139
144
  ```js
140
- var re = pathToRegexp('/:foo/(.*)', keys)
145
+ var re = pathToRegexp('/:foo/(.*)')
141
146
  // keys = [{ name: 'foo', ... }, { name: 0, ... }]
142
147
 
143
148
  re.exec('/test/route')
144
149
  //=> ['/test/route', 'test', 'route']
145
150
  ```
146
151
 
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
152
  ### Parse
160
153
 
161
154
  The parse function is exposed via `pathToRegexp.parse`. This will return an array of strings and keys.
@@ -173,11 +166,11 @@ console.log(tokens[2])
173
166
  //=> { name: 0, prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '.*' }
174
167
  ```
175
168
 
176
- **Note:** This method only works with Express-style strings.
169
+ **Note:** This method only works with strings.
177
170
 
178
171
  ### Compile ("Reverse" Path-To-RegExp)
179
172
 
180
- Path-To-RegExp exposes a compile function for transforming an Express-style path into a valid path.
173
+ Path-To-RegExp exposes a compile function for transforming a string into a valid path.
181
174
 
182
175
  ```js
183
176
  var toPath = pathToRegexp.compile('/user/:id')
@@ -186,8 +179,8 @@ toPath({ id: 123 }) //=> "/user/123"
186
179
  toPath({ id: 'café' }) //=> "/user/caf%C3%A9"
187
180
  toPath({ id: '/' }) //=> "/user/%2F"
188
181
 
189
- toPath({ id: ':' }) //=> "/user/%3A"
190
- toPath({ id: ':' }, { pretty: true }) //=> "/user/:"
182
+ toPath({ id: ':/' }) //=> "/user/%3A%2F"
183
+ toPath({ id: ':/' }, { encode: (x) => x }) //=> "/user/:/"
191
184
 
192
185
  var toPathRepeated = pathToRegexp.compile('/:segment+')
193
186
 
@@ -219,17 +212,15 @@ Path-To-RegExp exposes the two functions used internally that accept an array of
219
212
  * `repeat` Indicates the token is repeated (`boolean`)
220
213
  * `partial` Indicates this token is a partial path segment (`boolean`)
221
214
  * `pattern` The RegExp used to match this token (`string`)
222
- * `asterisk` Indicates the token is an `*` match (`boolean`)
223
215
 
224
216
  ## Compatibility with Express <= 4.x
225
217
 
226
218
  Path-To-RegExp breaks compatibility with Express <= `4.x`:
227
219
 
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
220
+ * RegExp special characters can only be used in a parameter
221
+ * Express.js 4.x used all `RegExp` special characters regardless of position - this considered a bug
232
222
  * Parameters have suffixes that augment meaning - `*`, `+` and `?`. E.g. `/:user*`
223
+ * No wildcard asterisk (`*`) - use parameters instead (`(.*)`)
233
224
 
234
225
  ## TypeScript
235
226
 
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`)
@@ -20,6 +14,14 @@ declare namespace pathToRegexp {
20
14
  * When `false` the path will match at the beginning. (default: `true`)
21
15
  */
22
16
  end?: boolean;
17
+ /**
18
+ * Sets the final character for non-ending optimistic matches. (default: `/`)
19
+ */
20
+ delimiter?: string;
21
+ /**
22
+ * List of characters that can also be "end" characters.
23
+ */
24
+ endsWith?: string | string[];
23
25
  }
24
26
 
25
27
  export interface ParseOptions {
@@ -27,6 +29,10 @@ declare namespace pathToRegexp {
27
29
  * Set the default delimiter for repeat parameters. (default: `'/'`)
28
30
  */
29
31
  delimiter?: string;
32
+ /**
33
+ * List of valid delimiter characters. (default: `'./'`)
34
+ */
35
+ delimiters?: string | string[];
30
36
  }
31
37
 
32
38
  /**
@@ -47,8 +53,7 @@ declare namespace pathToRegexp {
47
53
  /**
48
54
  * Transform an array of tokens into a matching regular expression.
49
55
  */
50
- export function tokensToRegExp (tokens: Token[], options?: RegExpOptions): PathRegExp;
51
- export function tokensToRegExp (tokens: Token[], keys?: Key[], options?: RegExpOptions): PathRegExp;
56
+ export function tokensToRegExp (tokens: Token[], keys?: Key[], options?: RegExpOptions): RegExp;
52
57
 
53
58
  export interface Key {
54
59
  name: string | number;
@@ -58,11 +63,13 @@ declare namespace pathToRegexp {
58
63
  repeat: boolean;
59
64
  pattern: string;
60
65
  partial: boolean;
61
- asterisk: boolean;
62
66
  }
63
67
 
64
68
  interface PathFunctionOptions {
65
- pretty?: boolean;
69
+ /**
70
+ * Function for encoding input strings for output.
71
+ */
72
+ encode?: (value: string) => string;
66
73
  }
67
74
 
68
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
@@ -111,30 +120,6 @@ function compile (str, options) {
111
120
  return tokensToFunction(parse(str, options))
112
121
  }
113
122
 
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
- })
136
- }
137
-
138
123
  /**
139
124
  * Expose a method for transforming tokens into the path function.
140
125
  */
@@ -149,55 +134,37 @@ function tokensToFunction (tokens) {
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
164
  segment = encode(value[j])
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) {
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))
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) {
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
  /**
@@ -258,17 +225,19 @@ function attachKeys (re, keys) {
258
225
  * @return {string}
259
226
  */
260
227
  function flags (options) {
261
- return options.sensitive ? '' : 'i'
228
+ return options && options.sensitive ? '' : 'i'
262
229
  }
263
230
 
264
231
  /**
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,24 +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 = ''
344
- var lastToken = tokens[tokens.length - 1]
345
- var endsWithSlash = typeof lastToken === 'string' && /\/$/.test(lastToken)
308
+ var isEndDelimited = false
346
309
 
347
310
  // Iterate over the tokens and create our regexp string.
348
311
  for (var i = 0; i < tokens.length; i++) {
@@ -350,47 +313,37 @@ function tokensToRegExp (tokens, keys, options) {
350
313
 
351
314
  if (typeof token === 'string') {
352
315
  route += escapeString(token)
316
+ isEndDelimited = i === tokens.length - 1 && delimiters.indexOf(token[token.length - 1]) > -1
353
317
  } else {
354
318
  var prefix = escapeString(token.prefix)
355
- var capture = '(?:' + token.pattern + ')'
356
-
357
- keys.push(token)
319
+ var capture = token.repeat
320
+ ? '(?:' + token.pattern + ')(?:' + prefix + '(?:' + token.pattern + '))*'
321
+ : token.pattern
358
322
 
359
- if (token.repeat) {
360
- capture += '(?:' + prefix + capture + ')*'
361
- }
323
+ if (keys) keys.push(token)
362
324
 
363
325
  if (token.optional) {
364
- if (!token.partial) {
365
- capture = '(?:' + prefix + '(' + capture + '))?'
326
+ if (token.partial) {
327
+ route += prefix + '(' + capture + ')?'
366
328
  } else {
367
- capture = prefix + '(' + capture + ')?'
329
+ route += '(?:' + prefix + '(' + capture + '))?'
368
330
  }
369
331
  } else {
370
- capture = prefix + '(' + capture + ')'
332
+ route += prefix + '(' + capture + ')'
371
333
  }
372
-
373
- route += capture
374
334
  }
375
335
  }
376
336
 
377
- // In non-strict mode we allow a slash at the end of match. If the path to
378
- // match already ends with a slash, we remove it for consistency. The slash
379
- // is valid at the end of a path match, not in the middle. This is important
380
- // in non-ending mode, where "/test/" shouldn't match "/test//route".
381
- if (!strict) {
382
- route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?'
383
- }
384
-
385
337
  if (end) {
386
- route += '$'
338
+ if (!strict) route += '(?:' + delimiter + ')?'
339
+
340
+ route += endsWith === '$' ? '$' : '(?=' + endsWith + ')'
387
341
  } else {
388
- // In non-ending mode, we need the capturing groups to match as much as
389
- // possible by using a positive lookahead to the end or next path segment.
390
- route += strict && endsWithSlash ? '' : '(?=\\/|$)'
342
+ if (!strict) route += '(?:' + delimiter + '(?=' + endsWith + '))?'
343
+ if (!isEndDelimited) route += '(?=' + delimiter + '|' + endsWith + ')'
391
344
  }
392
345
 
393
- return attachKeys(new RegExp('^' + route, flags(options)), keys)
346
+ return new RegExp('^' + route, flags(options))
394
347
  }
395
348
 
396
349
  /**
@@ -401,25 +354,18 @@ function tokensToRegExp (tokens, keys, options) {
401
354
  * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
402
355
  *
403
356
  * @param {(string|RegExp|Array)} path
404
- * @param {(Array|Object)=} keys
357
+ * @param {Array=} keys
405
358
  * @param {Object=} options
406
359
  * @return {!RegExp}
407
360
  */
408
361
  function pathToRegexp (path, keys, options) {
409
- if (!isarray(keys)) {
410
- options = /** @type {!Object} */ (keys || options)
411
- keys = []
412
- }
413
-
414
- options = options || {}
415
-
416
362
  if (path instanceof RegExp) {
417
- return regexpToRegexp(path, /** @type {!Array} */ (keys))
363
+ return regexpToRegexp(path, keys)
418
364
  }
419
365
 
420
- if (isarray(path)) {
421
- return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
366
+ if (Array.isArray(path)) {
367
+ return arrayToRegexp(/** @type {!Array} */ (path), keys, options)
422
368
  }
423
369
 
424
- return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
370
+ return stringToRegexp(/** @type {string} */ (path), keys, options)
425
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.6.0",
4
+ "version": "2.1.0",
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
  }