path-to-regexp
Advanced tools
Comparing version 1.0.3 to 1.1.0
308
index.js
@@ -1,2 +0,2 @@ | ||
var isArray = require('isarray'); | ||
var isarray = require('isarray') | ||
@@ -6,3 +6,5 @@ /** | ||
*/ | ||
module.exports = pathToRegexp; | ||
module.exports = pathToRegexp | ||
module.exports.parse = parse | ||
module.exports.compile = compile | ||
@@ -23,8 +25,156 @@ /** | ||
// "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined] | ||
'([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?', | ||
// Match regexp special characters that are always escaped. | ||
'([.+*?=^!:${}()[\\]|\\/])' | ||
].join('|'), 'g'); | ||
'([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?' | ||
].join('|'), 'g') | ||
/** | ||
* Parse a string for the raw tokens. | ||
* | ||
* @param {String} str | ||
* @return {Array} | ||
*/ | ||
function parse (str) { | ||
var tokens = [] | ||
var key = 0 | ||
var index = 0 | ||
var path = '' | ||
var res | ||
while ((res = PATH_REGEXP.exec(str)) != null) { | ||
var m = res[0] | ||
var escaped = res[1] | ||
var offset = res.index | ||
path += str.slice(index, offset) | ||
index = offset + m.length | ||
// Ignore already escaped sequences. | ||
if (escaped) { | ||
path += escaped[1] | ||
continue | ||
} | ||
// Push the current path onto the tokens. | ||
if (path) { | ||
tokens.push(path) | ||
path = '' | ||
} | ||
var prefix = res[2] | ||
var name = res[3] | ||
var capture = res[4] | ||
var group = res[5] | ||
var suffix = res[6] | ||
var repeat = suffix === '+' || suffix === '*' | ||
var optional = suffix === '?' || suffix === '*' | ||
var delimiter = prefix || '/' | ||
tokens.push({ | ||
name: name || key++, | ||
prefix: prefix || '', | ||
delimiter: delimiter, | ||
optional: optional, | ||
repeat: repeat, | ||
pattern: escapeGroup(capture || group || '[^' + delimiter + ']+?') | ||
}) | ||
} | ||
// Match any characters still remaining. | ||
if (index < str.length) { | ||
path += str.substr(index) | ||
} | ||
// If the path exists, push it onto the end. | ||
if (path) { | ||
tokens.push(path) | ||
} | ||
return tokens | ||
} | ||
/** | ||
* Compile a string to a template function for the path. | ||
* | ||
* @param {String} str | ||
* @return {Function} | ||
*/ | ||
function compile (str) { | ||
var keys = parse(str) | ||
// Compile all the patterns before compilation. | ||
for (var i = 0; i < keys.length; i++) { | ||
if (typeof keys[i] === 'object') { | ||
keys[i].regexp = new RegExp('^' + keys[i].pattern + '$') | ||
} | ||
} | ||
return function (obj) { | ||
var path = '' | ||
obj = obj || {} | ||
for (var i = 0; i < keys.length; i++) { | ||
var key = keys[i] | ||
if (typeof key === 'string') { | ||
path += key | ||
continue | ||
} | ||
var value = obj[key.name] | ||
if (value == null) { | ||
if (key.optional) { | ||
continue | ||
} else { | ||
throw new TypeError('Expected "' + key.name + '" to be defined') | ||
} | ||
} | ||
if (isarray(value)) { | ||
if (!key.repeat) { | ||
throw new TypeError('Expected "' + key.name + '" to not repeat') | ||
} | ||
if (value.length === 0) { | ||
if (key.optional) { | ||
continue | ||
} else { | ||
throw new TypeError('Expected "' + key.name + '" to not be empty') | ||
} | ||
} | ||
for (var j = 0; j < value.length; j++) { | ||
if (!key.regexp.test(value[j])) { | ||
throw new TypeError('Expected all "' + key.name + '" to match "' + key.pattern + '"') | ||
} | ||
path += (j === 0 ? key.prefix : key.delimiter) + encodeURIComponent(value[j]) | ||
} | ||
continue | ||
} | ||
if (!key.regexp.test(value)) { | ||
throw new TypeError('Expected "' + key.name + '" to match "' + key.pattern + '"') | ||
} | ||
path += key.prefix + encodeURIComponent(value) | ||
} | ||
return path | ||
} | ||
} | ||
/** | ||
* Escape a regular expression string. | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function escapeString (str) { | ||
return str.replace(/([.+*?=^!:${}()[\]|\/])/g, '\\$1') | ||
} | ||
/** | ||
* Escape the capturing group by escaping special characters and meaning. | ||
@@ -36,3 +186,3 @@ * | ||
function escapeGroup (group) { | ||
return group.replace(/([=!:$\/()])/g, '\\$1'); | ||
return group.replace(/([=!:$\/()])/g, '\\$1') | ||
} | ||
@@ -48,4 +198,4 @@ | ||
function attachKeys (re, keys) { | ||
re.keys = keys; | ||
return re; | ||
re.keys = keys | ||
return re | ||
} | ||
@@ -60,3 +210,3 @@ | ||
function flags (options) { | ||
return options.sensitive ? '' : 'i'; | ||
return options.sensitive ? '' : 'i' | ||
} | ||
@@ -73,3 +223,3 @@ | ||
// Use a negative lookahead to match only capturing groups. | ||
var groups = path.source.match(/\((?!\?)/g); | ||
var groups = path.source.match(/\((?!\?)/g) | ||
@@ -79,11 +229,13 @@ if (groups) { | ||
keys.push({ | ||
name: i, | ||
name: i, | ||
prefix: null, | ||
delimiter: null, | ||
optional: false, | ||
repeat: false | ||
}); | ||
optional: false, | ||
repeat: false, | ||
pattern: null | ||
}) | ||
} | ||
} | ||
return attachKeys(path, keys); | ||
return attachKeys(path, keys) | ||
} | ||
@@ -100,57 +252,76 @@ | ||
function arrayToRegexp (path, keys, options) { | ||
var parts = []; | ||
var parts = [] | ||
for (var i = 0; i < path.length; i++) { | ||
parts.push(pathToRegexp(path[i], keys, options).source); | ||
parts.push(pathToRegexp(path[i], keys, options).source) | ||
} | ||
var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)); | ||
return attachKeys(regexp, keys); | ||
var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)) | ||
return attachKeys(regexp, keys) | ||
} | ||
/** | ||
* Replace the specific tags with regexp strings. | ||
* Create a path regexp from string input. | ||
* | ||
* @param {String} path | ||
* @param {Array} keys | ||
* @return {String} | ||
* @param {Object} options | ||
* @return {RegExp} | ||
*/ | ||
function replacePath (path, keys) { | ||
var index = 0; | ||
function stringToRegexp (path, keys, options) { | ||
var strict = options.strict | ||
var end = options.end !== false | ||
var route = '' | ||
var endsWithSlash = path.charAt(path.length - 1) === '/' | ||
var tokens = parse(path) | ||
function replace (_, escaped, prefix, key, capture, group, suffix, escape) { | ||
if (escaped) { | ||
return escaped; | ||
} | ||
// Iterate over the tokens and create our regexp string. | ||
for (var i = 0; i < tokens.length; i++) { | ||
var token = tokens[i] | ||
if (escape) { | ||
return '\\' + escape; | ||
} | ||
if (typeof token === 'string') { | ||
route += escapeString(token) | ||
} else { | ||
var prefix = escapeString(token.prefix) | ||
var capture = token.pattern | ||
var repeat = suffix === '+' || suffix === '*'; | ||
var optional = suffix === '?' || suffix === '*'; | ||
// Push non-string tokens into the keys array. | ||
keys.push(token) | ||
keys.push({ | ||
name: key || index++, | ||
delimiter: prefix || '/', | ||
optional: optional, | ||
repeat: repeat | ||
}); | ||
if (token.repeat) { | ||
capture += '(?:' + prefix + capture + ')*' | ||
} | ||
prefix = prefix ? ('\\' + prefix) : ''; | ||
capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?'); | ||
if (token.optional) { | ||
if (prefix) { | ||
capture = '(?:' + prefix + '(' + capture + '))?' | ||
} else { | ||
capture = '(' + capture + ')?' | ||
} | ||
} else { | ||
capture = prefix + '(' + capture + ')' | ||
} | ||
if (repeat) { | ||
capture = capture + '(?:' + prefix + capture + ')*'; | ||
route += capture | ||
} | ||
} | ||
if (optional) { | ||
return '(?:' + prefix + '(' + capture + '))?'; | ||
} | ||
// In non-strict mode we allow a slash at the end of match. If the path to | ||
// match already ends with a slash, we remove it for consistency. The slash | ||
// is valid at the end of a path match, not in the middle. This is important | ||
// in non-ending mode, where "/test/" shouldn't match "/test//route". | ||
if (!strict) { | ||
route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?' | ||
} | ||
// Basic parameter support. | ||
return prefix + '(' + capture + ')'; | ||
if (end) { | ||
route += '$' | ||
} else { | ||
// In non-ending mode, we need the capturing groups to match as much as | ||
// possible by using a positive lookahead to the end or next path segment. | ||
route += strict && endsWithSlash ? '' : '(?=\\/|$)' | ||
} | ||
return path.replace(PATH_REGEXP, replace); | ||
return attachKeys(new RegExp('^' + route, flags(options)), keys) | ||
} | ||
@@ -171,41 +342,20 @@ | ||
function pathToRegexp (path, keys, options) { | ||
keys = keys || []; | ||
keys = keys || [] | ||
if (!isArray(keys)) { | ||
options = keys; | ||
keys = []; | ||
if (!isarray(keys)) { | ||
options = keys | ||
keys = [] | ||
} else if (!options) { | ||
options = {}; | ||
options = {} | ||
} | ||
if (path instanceof RegExp) { | ||
return regexpToRegexp(path, keys, options); | ||
return regexpToRegexp(path, keys, options) | ||
} | ||
if (isArray(path)) { | ||
return arrayToRegexp(path, keys, options); | ||
if (isarray(path)) { | ||
return arrayToRegexp(path, keys, options) | ||
} | ||
var strict = options.strict; | ||
var end = options.end !== false; | ||
var route = replacePath(path, keys); | ||
var endsWithSlash = path.charAt(path.length - 1) === '/'; | ||
// In non-strict mode we allow a slash at the end of match. If the path to | ||
// match already ends with a slash, we remove it for consistency. The slash | ||
// is valid at the end of a path match, not in the middle. This is important | ||
// in non-ending mode, where "/test/" shouldn't match "/test//route". | ||
if (!strict) { | ||
route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?'; | ||
} | ||
if (end) { | ||
route += '$'; | ||
} else { | ||
// In non-ending mode, we need the capturing groups to match as much as | ||
// possible by using a positive lookahead to the end or next path segment. | ||
route += strict && endsWithSlash ? '' : '(?=\\/|$)'; | ||
} | ||
return attachKeys(new RegExp('^' + route, flags(options)), keys); | ||
return stringToRegexp(path, keys, options) | ||
} |
{ | ||
"name": "path-to-regexp", | ||
"description": "Express style path to RegExp utility", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"files": [ | ||
@@ -10,3 +10,6 @@ "index.js", | ||
"scripts": { | ||
"test": "istanbul cover node_modules/mocha/bin/_mocha -- -R spec" | ||
"lint": "standard", | ||
"test-spec": "mocha -R spec --bail", | ||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- -R spec", | ||
"test": "npm run lint && npm run test-cov" | ||
}, | ||
@@ -30,4 +33,7 @@ "keywords": [ | ||
"devDependencies": { | ||
"chai": "^2.3.0", | ||
"istanbul": "~0.3.0", | ||
"mocha": "~1.21.4" | ||
"mocha": "~2.2.4", | ||
"pre-commit": "~1.0.5", | ||
"standard": "~3.7.3" | ||
}, | ||
@@ -34,0 +40,0 @@ "dependencies": { |
# Path-to-RegExp | ||
Turn an Express-style path string such as `/user/:name` into a regular expression. | ||
> Turn an Express-style path string such as `/user/:name` into a regular expression. | ||
@@ -21,5 +21,7 @@ [![NPM version][npm-image]][npm-url] | ||
```javascript | ||
var pathToRegexp = require('path-to-regexp'); | ||
var pathToRegexp = require('path-to-regexp') | ||
// pathToRegexp(path, keys, options); | ||
// pathToRegexp(path, keys, options) | ||
// pathToRegexp.parse(path) | ||
// pathToRegexp.compile(path) | ||
``` | ||
@@ -35,6 +37,6 @@ | ||
```javascript | ||
var keys = []; | ||
var re = pathToRegexp('/foo/:bar', keys); | ||
var keys = [] | ||
var re = pathToRegexp('/foo/:bar', keys) | ||
// re = /^\/foo\/([^\/]+?)\/?$/i | ||
// keys = [{ name: 'bar', delimiter: '/', repeat: false, optional: false }] | ||
// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }] | ||
``` | ||
@@ -51,6 +53,6 @@ | ||
```js | ||
var re = pathToRegexp('/:foo/:bar', keys); | ||
var re = pathToRegexp('/:foo/:bar', keys) | ||
// keys = [{ name: 'foo', ... }, { name: 'bar', ... }] | ||
re.exec('/test/route'); | ||
re.exec('/test/route') | ||
//=> ['/test/route', 'test', 'route'] | ||
@@ -66,9 +68,9 @@ ``` | ||
```js | ||
var re = pathToRegexp('/:foo/:bar?', keys); | ||
var re = pathToRegexp('/:foo/:bar?', keys) | ||
// keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }] | ||
re.exec('/test'); | ||
re.exec('/test') | ||
//=> ['/test', 'test', undefined] | ||
re.exec('/test/route'); | ||
re.exec('/test/route') | ||
//=> ['/test', 'test', 'route'] | ||
@@ -82,9 +84,9 @@ ``` | ||
```js | ||
var re = pathToRegexp('/:foo*', keys); | ||
var re = pathToRegexp('/:foo*', keys) | ||
// keys = [{ name: 'foo', delimiter: '/', optional: true, repeat: true }] | ||
re.exec('/'); | ||
re.exec('/') | ||
//=> ['/', undefined] | ||
re.exec('/bar/baz'); | ||
re.exec('/bar/baz') | ||
//=> ['/bar/baz', 'bar/baz'] | ||
@@ -98,9 +100,9 @@ ``` | ||
```js | ||
var re = pathToRegexp('/:foo+', keys); | ||
var re = pathToRegexp('/:foo+', keys) | ||
// keys = [{ name: 'foo', delimiter: '/', optional: false, repeat: true }] | ||
re.exec('/'); | ||
re.exec('/') | ||
//=> null | ||
re.exec('/bar/baz'); | ||
re.exec('/bar/baz') | ||
//=> ['/bar/baz', 'bar/baz'] | ||
@@ -114,9 +116,9 @@ ``` | ||
```js | ||
var re = pathToRegexp('/:foo(\\d+)', keys); | ||
var re = pathToRegexp('/:foo(\\d+)', keys) | ||
// keys = [{ name: 'foo', ... }] | ||
re.exec('/123'); | ||
re.exec('/123') | ||
//=> ['/123', '123'] | ||
re.exec('/abc'); | ||
re.exec('/abc') | ||
//=> null | ||
@@ -130,9 +132,43 @@ ``` | ||
```js | ||
var re = pathToRegexp('/:foo/(.*)', keys); | ||
var re = pathToRegexp('/:foo/(.*)', keys) | ||
// keys = [{ name: 'foo', ... }, { name: '0', ... }] | ||
re.exec('/test/route'); | ||
re.exec('/test/route') | ||
//=> ['/test/route', 'test', 'route'] | ||
``` | ||
### Parse | ||
The parse function is exposed via `pathToRegexp.parse`. This will yield an array of strings and keys. | ||
```js | ||
var tokens = pathToRegexp.parse('/route/:foo/(.*)') | ||
console.log(tokens[0]) | ||
//=> "/route" | ||
console.log(tokens[1]) | ||
//=> { name: 'foo', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' } | ||
console.log(tokens[2]) | ||
//=> { name: 0, prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '.*' } | ||
``` | ||
**Note:** This method only works with strings. | ||
### Compile ("Reverse" Path-To-RegExp) | ||
Path-To-RegExp exposes a compile function for transforming an express path into valid path. Confusing enough? This example will straighten everything out for you. | ||
```js | ||
var toPath = pathToRegexp.compile('/user/:id') | ||
var result = toPath({ id: 123 }) | ||
console.log(result) | ||
//=> "/user/123" | ||
``` | ||
**Note:** The generated function will throw on any invalid input. It will execute all necessary checks to ensure the generated path is valid. This method only works with strings. | ||
## Compatibility with Express <= 4.x | ||
@@ -142,3 +178,3 @@ | ||
* RegExp special characters can now be used in the regular path. E.g. `/user[(\\d+)]` | ||
* RegExp special characters can now be used in the regular path. E.g. `/user/(\\d+)` | ||
* All RegExp special characters can now be used inside the custom match. E.g. `/:user(.*)` | ||
@@ -145,0 +181,0 @@ * No more support for asterisk matching - use an explicit parameter instead. E.g. `/(.*)` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
18154
297
196
5