path-parser
Advanced tools
Comparing version 1.0.4 to 2.0.0
@@ -0,1 +1,17 @@ | ||
<a name="2.0.0"></a> | ||
# [2.0.0](https://github.com/troch/path-parser/compare/v1.0.4...v2.0.0) (2016-10-12) | ||
### Features | ||
* add a 'delimited' option to partialMatch ([969c1bf](https://github.com/troch/path-parser/commit/969c1bf)) | ||
* add search-params as a dependency ([6ab04d7](https://github.com/troch/path-parser/commit/6ab04d7)) | ||
### BREAKING CHANGES | ||
* `.match()` renamed to `.test()` and now takes an options object as a second parameter | ||
* `.partialMatch()` renamed to `.partialTest()` and now takes an options object as a second parameter. Option `delimited` is by default true, meaning a partial will be successful if the partially matched path is consumed up to a delimiter (`?`, `/`, `.`, `;`) | ||
<a name="1.0.4"></a> | ||
@@ -2,0 +18,0 @@ ## [1.0.4](https://github.com/troch/path-parser/compare/v1.0.3...v1.0.4) (2016-06-30) |
@@ -1,364 +0,572 @@ | ||
define('Path', function () { 'use strict'; | ||
define('Path', ['search-params'], function (searchParams) { 'use strict'; | ||
var babelHelpers = {}; | ||
var asyncGenerator = function () { | ||
function AwaitValue(value) { | ||
this.value = value; | ||
} | ||
function babelHelpers_classCallCheck (instance, Constructor) { | ||
if (!(instance instanceof Constructor)) { | ||
throw new TypeError("Cannot call a class as a function"); | ||
} | ||
}; | ||
function AsyncGenerator(gen) { | ||
var front, back; | ||
var babelHelpers_createClass = (function () { | ||
function defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
function send(key, arg) { | ||
return new Promise(function (resolve, reject) { | ||
var request = { | ||
key: key, | ||
arg: arg, | ||
resolve: resolve, | ||
reject: reject, | ||
next: null | ||
}; | ||
if (back) { | ||
back = back.next = request; | ||
} else { | ||
front = back = request; | ||
resume(key, arg); | ||
} | ||
}); | ||
} | ||
function resume(key, arg) { | ||
try { | ||
var result = gen[key](arg); | ||
var value = result.value; | ||
if (value instanceof AwaitValue) { | ||
Promise.resolve(value.value).then(function (arg) { | ||
resume("next", arg); | ||
}, function (arg) { | ||
resume("throw", arg); | ||
}); | ||
} else { | ||
settle(result.done ? "return" : "normal", result.value); | ||
} | ||
} catch (err) { | ||
settle("throw", err); | ||
} | ||
} | ||
return function (Constructor, protoProps, staticProps) { | ||
if (protoProps) defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
}; | ||
})(); | ||
function settle(type, value) { | ||
switch (type) { | ||
case "return": | ||
front.resolve({ | ||
value: value, | ||
done: true | ||
}); | ||
break; | ||
var defaultOrConstrained = function defaultOrConstrained(match) { | ||
return '(' + (match ? match.replace(/(^<|>$)/g, '') : '[a-zA-Z0-9-_.~%]+') + ')'; | ||
case "throw": | ||
front.reject(value); | ||
break; | ||
default: | ||
front.resolve({ | ||
value: value, | ||
done: false | ||
}); | ||
break; | ||
} | ||
front = front.next; | ||
if (front) { | ||
resume(front.key, front.arg); | ||
} else { | ||
back = null; | ||
} | ||
} | ||
this._invoke = send; | ||
if (typeof gen.return !== "function") { | ||
this.return = undefined; | ||
} | ||
} | ||
if (typeof Symbol === "function" && Symbol.asyncIterator) { | ||
AsyncGenerator.prototype[Symbol.asyncIterator] = function () { | ||
return this; | ||
}; | ||
} | ||
var rules = [{ | ||
// An URL can contain a parameter :paramName | ||
// - and _ are allowed but not in last position | ||
name: 'url-parameter', | ||
pattern: /^:([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Url parameter (splat) | ||
name: 'url-parameter-splat', | ||
pattern: /^\*([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/, | ||
regex: /([^\?]*)/ | ||
}, { | ||
name: 'url-parameter-matrix', | ||
pattern: /^\;([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(';' + match[1] + '=' + defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter-bracket', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(?:\[\])/ | ||
}, // regex: match => new RegExp('(?=(\?|.*&)' + match[0] + '(?=(\=|&|$)))') | ||
{ | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/ | ||
}, // regex: match => new RegExp('(?=(\?|.*&)' + match[0] + '(?=(\=|&|$)))') | ||
{ | ||
// Delimiter / | ||
name: 'delimiter', | ||
pattern: /^(\/|\?)/, | ||
regex: function regex(match) { | ||
return new RegExp('\\' + match[0]); | ||
} | ||
}, { | ||
// Sub delimiters | ||
name: 'sub-delimiter', | ||
pattern: /^(\!|\&|\-|_|\.|;)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}, { | ||
// Unmatched fragment (until delimiter is found) | ||
name: 'fragment', | ||
pattern: /^([0-9a-zA-Z]+)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}]; | ||
AsyncGenerator.prototype.next = function (arg) { | ||
return this._invoke("next", arg); | ||
}; | ||
var tokenise = function tokenise(str) { | ||
var tokens = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; | ||
AsyncGenerator.prototype.throw = function (arg) { | ||
return this._invoke("throw", arg); | ||
}; | ||
// Look for a matching rule | ||
var matched = rules.some(function (rule) { | ||
var match = str.match(rule.pattern); | ||
if (!match) return false; | ||
AsyncGenerator.prototype.return = function (arg) { | ||
return this._invoke("return", arg); | ||
}; | ||
tokens.push({ | ||
type: rule.name, | ||
match: match[0], | ||
val: match.slice(1, 2), | ||
otherVal: match.slice(2), | ||
regex: rule.regex instanceof Function ? rule.regex(match) : rule.regex | ||
}); | ||
return { | ||
wrap: function (fn) { | ||
return function () { | ||
return new AsyncGenerator(fn.apply(this, arguments)); | ||
}; | ||
}, | ||
await: function (value) { | ||
return new AwaitValue(value); | ||
} | ||
}; | ||
}(); | ||
if (match[0].length < str.length) tokens = tokenise(str.substr(match[0].length), tokens); | ||
return true; | ||
var classCallCheck = function (instance, Constructor) { | ||
if (!(instance instanceof Constructor)) { | ||
throw new TypeError("Cannot call a class as a function"); | ||
} | ||
}; | ||
var createClass = function () { | ||
function defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
return function (Constructor, protoProps, staticProps) { | ||
if (protoProps) defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
}; | ||
}(); | ||
var _extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
var get = function get(object, property, receiver) { | ||
if (object === null) object = Function.prototype; | ||
var desc = Object.getOwnPropertyDescriptor(object, property); | ||
if (desc === undefined) { | ||
var parent = Object.getPrototypeOf(object); | ||
if (parent === null) { | ||
return undefined; | ||
} else { | ||
return get(parent, property, receiver); | ||
} | ||
} else if ("value" in desc) { | ||
return desc.value; | ||
} else { | ||
var getter = desc.get; | ||
if (getter === undefined) { | ||
return undefined; | ||
} | ||
return getter.call(receiver); | ||
} | ||
}; | ||
var set = function set(object, property, value, receiver) { | ||
var desc = Object.getOwnPropertyDescriptor(object, property); | ||
if (desc === undefined) { | ||
var parent = Object.getPrototypeOf(object); | ||
if (parent !== null) { | ||
set(parent, property, value, receiver); | ||
} | ||
} else if ("value" in desc && desc.writable) { | ||
desc.value = value; | ||
} else { | ||
var setter = desc.set; | ||
if (setter !== undefined) { | ||
setter.call(receiver, value); | ||
} | ||
} | ||
return value; | ||
}; | ||
var defaultOrConstrained = function defaultOrConstrained(match) { | ||
return '(' + (match ? match.replace(/(^<|>$)/g, '') : '[a-zA-Z0-9-_.~%]+') + ')'; | ||
}; | ||
var rules = [{ | ||
// An URL can contain a parameter :paramName | ||
// - and _ are allowed but not in last position | ||
name: 'url-parameter', | ||
pattern: /^:([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Url parameter (splat) | ||
name: 'url-parameter-splat', | ||
pattern: /^\*([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/, | ||
regex: /([^\?]*)/ | ||
}, { | ||
name: 'url-parameter-matrix', | ||
pattern: /^\;([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(';' + match[1] + '=' + defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter-bracket', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(?:\[\])/ | ||
}, { | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/ | ||
}, { | ||
// Delimiter / | ||
name: 'delimiter', | ||
pattern: /^(\/|\?)/, | ||
regex: function regex(match) { | ||
return new RegExp('\\' + match[0]); | ||
} | ||
}, { | ||
// Sub delimiters | ||
name: 'sub-delimiter', | ||
pattern: /^(\!|\&|\-|_|\.|;)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}, { | ||
// Unmatched fragment (until delimiter is found) | ||
name: 'fragment', | ||
pattern: /^([0-9a-zA-Z]+)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}]; | ||
var exists = function exists(val) { | ||
return val !== undefined && val !== null; | ||
}; | ||
var tokenise = function tokenise(str) { | ||
var tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
// Look for a matching rule | ||
var matched = rules.some(function (rule) { | ||
var match = str.match(rule.pattern); | ||
if (!match) return false; | ||
tokens.push({ | ||
type: rule.name, | ||
match: match[0], | ||
val: match.slice(1, 2), | ||
otherVal: match.slice(2), | ||
regex: rule.regex instanceof Function ? rule.regex(match) : rule.regex | ||
}); | ||
// If no rules matched, throw an error (possible malformed path) | ||
if (!matched) { | ||
throw new Error('Could not parse path.'); | ||
} | ||
// Return tokens | ||
return tokens; | ||
}; | ||
if (match[0].length < str.length) tokens = tokenise(str.substr(match[0].length), tokens); | ||
return true; | ||
}); | ||
var optTrailingSlash = function optTrailingSlash(source, trailingSlash) { | ||
if (!trailingSlash) return source; | ||
return source.replace(/\\\/$/, '') + '(?:\\/)?'; | ||
}; | ||
// If no rules matched, throw an error (possible malformed path) | ||
if (!matched) { | ||
throw new Error('Could not parse path.'); | ||
} | ||
// Return tokens | ||
return tokens; | ||
}; | ||
var withoutBrackets = function withoutBrackets(param) { | ||
return param.replace(/\[\]$/, ''); | ||
}; | ||
var optTrailingSlash = function optTrailingSlash(source, trailingSlash) { | ||
if (!trailingSlash) return source; | ||
return source.replace(/\\\/$/, '') + '(?:\\/)?'; | ||
}; | ||
var appendQueryParam = function appendQueryParam(params, param) { | ||
var val = arguments.length <= 2 || arguments[2] === undefined ? '' : arguments[2]; | ||
var upToDelimiter = function upToDelimiter(source, delimiter) { | ||
if (!delimiter) return source; | ||
return source.replace(/\\(\/|\?|\.|;)$/, '') + '(\\/|\\?|\\.|;|$)'; | ||
}; | ||
if (/\[\]$/.test(param)) { | ||
param = withoutBrackets(param); | ||
val = [val]; | ||
} | ||
var existingVal = params[param]; | ||
var appendQueryParam = function appendQueryParam(params, param) { | ||
var val = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; | ||
if (existingVal === undefined) params[param] = val;else params[param] = Array.isArray(existingVal) ? existingVal.concat(val) : [existingVal, val]; | ||
if (/\[\]$/.test(param)) { | ||
param = searchParams.withoutBrackets(param); | ||
val = [val]; | ||
} | ||
var existingVal = params[param]; | ||
return params; | ||
}; | ||
if (existingVal === undefined) params[param] = val;else params[param] = Array.isArray(existingVal) ? existingVal.concat(val) : [existingVal, val]; | ||
var parseQueryParams = function parseQueryParams(path) { | ||
var searchPart = path.split('?')[1]; | ||
if (!searchPart) return {}; | ||
return params; | ||
}; | ||
return searchPart.split('&').map(function (_) { | ||
return _.split('='); | ||
}).reduce(function (obj, m) { | ||
return appendQueryParam(obj, m[0], m[1] ? decodeURIComponent(m[1]) : m[1]); | ||
}, {}); | ||
}; | ||
var parseQueryParams = function parseQueryParams(path) { | ||
var searchPart = searchParams.getSearch(path); | ||
if (!searchPart) return {}; | ||
var toSerialisable = function toSerialisable(val) { | ||
return val !== undefined && val !== null && val !== '' ? '=' + val : ''; | ||
}; | ||
return searchParams.toObject(searchParams.parse(searchPart)); | ||
}; | ||
var _serialise = function _serialise(key, val) { | ||
return Array.isArray(val) ? val.map(function (v) { | ||
function _serialise(key, val) { | ||
if (Array.isArray(val)) { | ||
return val.map(function (v) { | ||
return _serialise(key, v); | ||
}).join('&') : key + toSerialisable(val); | ||
}; | ||
}).join('&'); | ||
} | ||
var Path = (function () { | ||
babelHelpers_createClass(Path, null, [{ | ||
key: 'createPath', | ||
value: function createPath(path) { | ||
return new Path(path); | ||
} | ||
}, { | ||
key: 'serialise', | ||
value: function serialise(key, val) { | ||
return _serialise(key, val); | ||
} | ||
}]); | ||
if (val === true) { | ||
return key; | ||
} | ||
function Path(path) { | ||
babelHelpers_classCallCheck(this, Path); | ||
return key + '=' + val; | ||
} | ||
if (!path) throw new Error('Please supply a path'); | ||
this.path = path; | ||
this.tokens = tokenise(path); | ||
var Path = function () { | ||
createClass(Path, null, [{ | ||
key: 'createPath', | ||
value: function createPath(path) { | ||
return new Path(path); | ||
} | ||
}, { | ||
key: 'serialise', | ||
value: function serialise(key, val) { | ||
return _serialise(key, val); | ||
} | ||
}]); | ||
this.hasUrlParams = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasSpatParam = this.tokens.filter(function (t) { | ||
return (/splat$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasMatrixParams = this.tokens.filter(function (t) { | ||
return (/matrix$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasQueryParams = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
// Extract named parameters from tokens | ||
this.urlParams = !this.hasUrlParams ? [] : this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
return t.val.slice(0, 1); | ||
}) | ||
// Flatten | ||
.reduce(function (r, v) { | ||
return r.concat(v); | ||
}); | ||
// Query params | ||
this.queryParams = !this.hasQueryParams ? [] : this.tokens.filter(function (t) { | ||
return t.type === 'query-parameter'; | ||
}).map(function (t) { | ||
return t.val; | ||
}).reduce(function (r, v) { | ||
return r.concat(v); | ||
}, []); | ||
function Path(path) { | ||
classCallCheck(this, Path); | ||
this.queryParamsBr = !this.hasQueryParams ? [] : this.tokens.filter(function (t) { | ||
return (/-bracket$/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
return t.val; | ||
}).reduce(function (r, v) { | ||
return r.concat(v); | ||
}, []); | ||
if (!path) throw new Error('Please supply a path'); | ||
this.path = path; | ||
this.tokens = tokenise(path); | ||
this.params = this.urlParams.concat(this.queryParams).concat(this.queryParamsBr); | ||
// Check if hasQueryParams | ||
// Regular expressions for url part only (full and partial match) | ||
this.source = this.tokens.filter(function (t) { | ||
return t.regex !== undefined; | ||
}).map(function (r) { | ||
return r.regex.source; | ||
}).join(''); | ||
this.hasUrlParams = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasSpatParam = this.tokens.filter(function (t) { | ||
return (/splat$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasMatrixParams = this.tokens.filter(function (t) { | ||
return (/matrix$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasQueryParams = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
// Extract named parameters from tokens | ||
this.spatParams = this._getParams('url-parameter-splat'); | ||
this.urlParams = this._getParams(/^url-parameter/); | ||
// Query params | ||
this.queryParams = this._getParams('query-parameter'); | ||
this.queryParamsBr = this._getParams('query-parameter-bracket'); | ||
// All params | ||
this.params = this.urlParams.concat(this.queryParams).concat(this.queryParamsBr); | ||
// Check if hasQueryParams | ||
// Regular expressions for url part only (full and partial match) | ||
this.source = this.tokens.filter(function (t) { | ||
return t.regex !== undefined; | ||
}).map(function (r) { | ||
return r.regex.source; | ||
}).join(''); | ||
} | ||
createClass(Path, [{ | ||
key: '_getParams', | ||
value: function _getParams(type) { | ||
var predicate = type instanceof RegExp ? function (t) { | ||
return type.test(t.type); | ||
} : function (t) { | ||
return t.type === type; | ||
}; | ||
return this.tokens.filter(predicate).map(function (t) { | ||
return t.val[0]; | ||
}); | ||
} | ||
}, { | ||
key: '_isSpatParam', | ||
value: function _isSpatParam(name) { | ||
return this.spatParams.indexOf(name) !== -1; | ||
} | ||
}, { | ||
key: '_urlTest', | ||
value: function _urlTest(path, regex) { | ||
var _this = this; | ||
babelHelpers_createClass(Path, [{ | ||
key: '_urlMatch', | ||
value: function _urlMatch(path, regex) { | ||
var _this = this; | ||
var match = path.match(regex); | ||
if (!match) return null;else if (!this.urlParams.length) return {}; | ||
// Reduce named params to key-value pairs | ||
return match.slice(1, this.urlParams.length + 1).reduce(function (params, m, i) { | ||
params[_this.urlParams[i]] = decodeURIComponent(m); | ||
return params; | ||
}, {}); | ||
} | ||
}, { | ||
key: 'test', | ||
value: function test(path, opts) { | ||
var _this2 = this; | ||
var match = path.match(regex); | ||
if (!match) return null;else if (!this.urlParams.length) return {}; | ||
// Reduce named params to key-value pairs | ||
return match.slice(1, this.urlParams.length + 1).reduce(function (params, m, i) { | ||
params[_this.urlParams[i]] = decodeURIComponent(m); | ||
return params; | ||
}, {}); | ||
} | ||
}, { | ||
key: 'match', | ||
value: function match(path) { | ||
var _this2 = this; | ||
var options = _extends({ trailingSlash: false }, opts); | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, options.trailingSlash); | ||
// Check if exact match | ||
var matched = this._urlTest(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
// If no match, or no query params, no need to go further | ||
if (!matched || !this.hasQueryParams) return matched; | ||
// Extract query params | ||
var queryParams = parseQueryParams(path); | ||
var unexpectedQueryParams = Object.keys(queryParams).filter(function (p) { | ||
return _this2.queryParams.concat(_this2.queryParamsBr).indexOf(p) === -1; | ||
}); | ||
var trailingSlash = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, trailingSlash); | ||
// Check if exact match | ||
var matched = this._urlMatch(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
// If no match, or no query params, no need to go further | ||
if (!matched || !this.hasQueryParams) return matched; | ||
// Extract query params | ||
var queryParams = parseQueryParams(path); | ||
var unexpectedQueryParams = Object.keys(queryParams).filter(function (p) { | ||
return _this2.queryParams.concat(_this2.queryParamsBr).indexOf(p) === -1; | ||
if (unexpectedQueryParams.length === 0) { | ||
// Extend url match | ||
Object.keys(queryParams).forEach(function (p) { | ||
return matched[p] = queryParams[p]; | ||
}); | ||
if (unexpectedQueryParams.length === 0) { | ||
// Extend url match | ||
Object.keys(queryParams).forEach(function (p) { | ||
return matched[p] = queryParams[p]; | ||
}); | ||
return matched; | ||
} | ||
return matched; | ||
} | ||
return null; | ||
} | ||
}, { | ||
key: 'partialTest', | ||
value: function partialTest(path, opts) { | ||
var _this3 = this; | ||
return null; | ||
} | ||
}, { | ||
key: 'partialMatch', | ||
value: function partialMatch(path) { | ||
var _this3 = this; | ||
var options = _extends({ delimited: true }, opts); | ||
// Check if partial match (start of given path matches regex) | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = upToDelimiter(this.source, options.delimited); | ||
var match = this._urlTest(path, new RegExp('^' + source)); | ||
var trailingSlash = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; | ||
if (!match) return match; | ||
// Check if partial match (start of given path matches regex) | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, trailingSlash); | ||
var match = this._urlMatch(path, new RegExp('^' + source)); | ||
if (!this.hasQueryParams) return match; | ||
if (!match) return match; | ||
var queryParams = parseQueryParams(path); | ||
if (!this.hasQueryParams) return match; | ||
Object.keys(queryParams).filter(function (p) { | ||
return _this3.queryParams.concat(_this3.queryParamsBr).indexOf(p) >= 0; | ||
}).forEach(function (p) { | ||
return appendQueryParam(match, p, queryParams[p]); | ||
}); | ||
var queryParams = parseQueryParams(path); | ||
return match; | ||
} | ||
}, { | ||
key: 'build', | ||
value: function build() { | ||
var _this4 = this; | ||
Object.keys(queryParams).filter(function (p) { | ||
return _this3.queryParams.concat(_this3.queryParamsBr).indexOf(p) >= 0; | ||
}).forEach(function (p) { | ||
return appendQueryParam(match, p, queryParams[p]); | ||
}); | ||
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return match; | ||
} | ||
}, { | ||
key: 'build', | ||
value: function build() { | ||
var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? { ignoreConstraints: false, ignoreSearch: false } : arguments[1]; | ||
var encodedParams = Object.keys(params).reduce(function (acc, key) { | ||
// Use encodeURI in case of spats | ||
if (params[key] === undefined) { | ||
acc[key] = undefined; | ||
} else { | ||
acc[key] = Array.isArray(params[key]) ? params[key].map(encodeURI) : encodeURI(params[key]); | ||
} | ||
var options = _extends({ ignoreConstraints: false, ignoreSearch: false }, opts); | ||
var encodedParams = Object.keys(params).reduce(function (acc, key) { | ||
if (!exists(params[key])) { | ||
return acc; | ||
}, {}); | ||
// Check all params are provided (not search parameters which are optional) | ||
if (this.urlParams.some(function (p) { | ||
return params[p] === undefined; | ||
})) throw new Error('Missing parameters'); | ||
} | ||
// Check constraints | ||
if (!opts.ignoreConstraints) { | ||
var constraintsPassed = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) && !/-splat$/.test(t.type) | ||
); | ||
}).every(function (t) { | ||
return new RegExp('^' + defaultOrConstrained(t.otherVal[0]) + '$').test(encodedParams[t.val]); | ||
}); | ||
var val = params[key]; | ||
var encode = _this4._isSpatParam(key) ? encodeURI : encodeURIComponent; | ||
if (!constraintsPassed) throw new Error('Some parameters are of invalid format'); | ||
if (typeof val === 'boolean') { | ||
acc[key] = val; | ||
} else if (Array.isArray(val)) { | ||
acc[key] = val.map(encode); | ||
} else { | ||
acc[key] = encode(val); | ||
} | ||
var base = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) === false | ||
return acc; | ||
}, {}); | ||
// Check all params are provided (not search parameters which are optional) | ||
if (this.urlParams.some(function (p) { | ||
return !exists(encodedParams[p]); | ||
})) throw new Error('Missing parameters'); | ||
// Check constraints | ||
if (!options.ignoreConstraints) { | ||
var constraintsPassed = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) && !/-splat$/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
if (t.type === 'url-parameter-matrix') return ';' + t.val + '=' + encodedParams[t.val[0]]; | ||
return (/^url-parameter/.test(t.type) ? encodedParams[t.val[0]] : t.match | ||
); | ||
}).join(''); | ||
}).every(function (t) { | ||
return new RegExp('^' + defaultOrConstrained(t.otherVal[0]) + '$').test(encodedParams[t.val]); | ||
}); | ||
if (opts.ignoreSearch) return base; | ||
if (!constraintsPassed) throw new Error('Some parameters are of invalid format'); | ||
} | ||
var queryParams = this.queryParams.concat(this.queryParamsBr.map(function (p) { | ||
return p + '[]'; | ||
})); | ||
var base = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) === false | ||
); | ||
}).map(function (t) { | ||
if (t.type === 'url-parameter-matrix') return ';' + t.val + '=' + encodedParams[t.val[0]]; | ||
return (/^url-parameter/.test(t.type) ? encodedParams[t.val[0]] : t.match | ||
); | ||
}).join(''); | ||
var searchPart = queryParams.filter(function (p) { | ||
return Object.keys(encodedParams).indexOf(withoutBrackets(p)) !== -1; | ||
}).map(function (p) { | ||
return _serialise(p, encodedParams[withoutBrackets(p)]); | ||
}).join('&'); | ||
if (options.ignoreSearch) return base; | ||
return base + (searchPart ? '?' + searchPart : ''); | ||
} | ||
}]); | ||
return Path; | ||
})(); | ||
var queryParams = this.queryParams.concat(this.queryParamsBr.map(function (p) { | ||
return p + '[]'; | ||
})); | ||
var searchPart = queryParams.filter(function (p) { | ||
return Object.keys(encodedParams).indexOf(searchParams.withoutBrackets(p)) !== -1; | ||
}).map(function (p) { | ||
return _serialise(p, encodedParams[searchParams.withoutBrackets(p)]); | ||
}).join('&'); | ||
return base + (searchPart ? '?' + searchPart : ''); | ||
} | ||
}]); | ||
return Path; | ||
}(); | ||
}); | ||
return Path; | ||
}); |
'use strict'; | ||
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -9,2 +7,8 @@ value: true | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _searchParams = require('search-params'); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
@@ -40,4 +44,3 @@ | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(?:\[\])/ | ||
}, // regex: match => new RegExp('(?=(\?|.*&)' + match[0] + '(?=(\=|&|$)))') | ||
{ | ||
}, { | ||
// Query parameter: ?param1¶m2 | ||
@@ -47,4 +50,3 @@ // ?:param1&:param2 | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/ | ||
}, // regex: match => new RegExp('(?=(\?|.*&)' + match[0] + '(?=(\=|&|$)))') | ||
{ | ||
}, { | ||
// Delimiter / | ||
@@ -72,4 +74,8 @@ name: 'delimiter', | ||
var exists = function exists(val) { | ||
return val !== undefined && val !== null; | ||
}; | ||
var tokenise = function tokenise(str) { | ||
var tokens = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; | ||
var tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
@@ -106,11 +112,12 @@ // Look for a matching rule | ||
var withoutBrackets = function withoutBrackets(param) { | ||
return param.replace(/\[\]$/, ''); | ||
var upToDelimiter = function upToDelimiter(source, delimiter) { | ||
if (!delimiter) return source; | ||
return source.replace(/\\(\/|\?|\.|;)$/, '') + '(\\/|\\?|\\.|;|$)'; | ||
}; | ||
var appendQueryParam = function appendQueryParam(params, param) { | ||
var val = arguments.length <= 2 || arguments[2] === undefined ? '' : arguments[2]; | ||
var val = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; | ||
if (/\[\]$/.test(param)) { | ||
param = withoutBrackets(param); | ||
param = (0, _searchParams.withoutBrackets)(param); | ||
val = [val]; | ||
@@ -126,23 +133,23 @@ } | ||
var parseQueryParams = function parseQueryParams(path) { | ||
var searchPart = path.split('?')[1]; | ||
var searchPart = (0, _searchParams.getSearch)(path); | ||
if (!searchPart) return {}; | ||
return searchPart.split('&').map(function (_) { | ||
return _.split('='); | ||
}).reduce(function (obj, m) { | ||
return appendQueryParam(obj, m[0], m[1] ? decodeURIComponent(m[1]) : m[1]); | ||
}, {}); | ||
return (0, _searchParams.toObject)((0, _searchParams.parse)(searchPart)); | ||
}; | ||
var toSerialisable = function toSerialisable(val) { | ||
return val !== undefined && val !== null && val !== '' ? '=' + val : ''; | ||
}; | ||
function _serialise(key, val) { | ||
if (Array.isArray(val)) { | ||
return val.map(function (v) { | ||
return _serialise(key, v); | ||
}).join('&'); | ||
} | ||
var _serialise = function _serialise(key, val) { | ||
return Array.isArray(val) ? val.map(function (v) { | ||
return _serialise(key, v); | ||
}).join('&') : key + toSerialisable(val); | ||
}; | ||
if (val === true) { | ||
return key; | ||
} | ||
var Path = (function () { | ||
return key + '=' + val; | ||
} | ||
var Path = function () { | ||
_createClass(Path, null, [{ | ||
@@ -184,30 +191,8 @@ key: 'createPath', | ||
// Extract named parameters from tokens | ||
this.urlParams = !this.hasUrlParams ? [] : this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
return t.val.slice(0, 1); | ||
}) | ||
// Flatten | ||
.reduce(function (r, v) { | ||
return r.concat(v); | ||
}); | ||
this.spatParams = this._getParams('url-parameter-splat'); | ||
this.urlParams = this._getParams(/^url-parameter/); | ||
// Query params | ||
this.queryParams = !this.hasQueryParams ? [] : this.tokens.filter(function (t) { | ||
return t.type === 'query-parameter'; | ||
}).map(function (t) { | ||
return t.val; | ||
}).reduce(function (r, v) { | ||
return r.concat(v); | ||
}, []); | ||
this.queryParamsBr = !this.hasQueryParams ? [] : this.tokens.filter(function (t) { | ||
return (/-bracket$/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
return t.val; | ||
}).reduce(function (r, v) { | ||
return r.concat(v); | ||
}, []); | ||
this.queryParams = this._getParams('query-parameter'); | ||
this.queryParamsBr = this._getParams('query-parameter-bracket'); | ||
// All params | ||
this.params = this.urlParams.concat(this.queryParams).concat(this.queryParamsBr); | ||
@@ -224,4 +209,22 @@ // Check if hasQueryParams | ||
_createClass(Path, [{ | ||
key: '_urlMatch', | ||
value: function _urlMatch(path, regex) { | ||
key: '_getParams', | ||
value: function _getParams(type) { | ||
var predicate = type instanceof RegExp ? function (t) { | ||
return type.test(t.type); | ||
} : function (t) { | ||
return t.type === type; | ||
}; | ||
return this.tokens.filter(predicate).map(function (t) { | ||
return t.val[0]; | ||
}); | ||
} | ||
}, { | ||
key: '_isSpatParam', | ||
value: function _isSpatParam(name) { | ||
return this.spatParams.indexOf(name) !== -1; | ||
} | ||
}, { | ||
key: '_urlTest', | ||
value: function _urlTest(path, regex) { | ||
var _this = this; | ||
@@ -238,12 +241,11 @@ | ||
}, { | ||
key: 'match', | ||
value: function match(path) { | ||
key: 'test', | ||
value: function test(path, opts) { | ||
var _this2 = this; | ||
var trailingSlash = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; | ||
var options = _extends({ trailingSlash: false }, opts); | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, trailingSlash); | ||
var source = optTrailingSlash(this.source, options.trailingSlash); | ||
// Check if exact match | ||
var matched = this._urlMatch(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
var matched = this._urlTest(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
// If no match, or no query params, no need to go further | ||
@@ -269,12 +271,11 @@ if (!matched || !this.hasQueryParams) return matched; | ||
}, { | ||
key: 'partialMatch', | ||
value: function partialMatch(path) { | ||
key: 'partialTest', | ||
value: function partialTest(path, opts) { | ||
var _this3 = this; | ||
var trailingSlash = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; | ||
var options = _extends({ delimited: true }, opts); | ||
// Check if partial match (start of given path matches regex) | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, trailingSlash); | ||
var match = this._urlMatch(path, new RegExp('^' + source)); | ||
var source = upToDelimiter(this.source, options.delimited); | ||
var match = this._urlTest(path, new RegExp('^' + source)); | ||
@@ -298,21 +299,34 @@ if (!match) return match; | ||
value: function build() { | ||
var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? { ignoreConstraints: false, ignoreSearch: false } : arguments[1]; | ||
var _this4 = this; | ||
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var options = _extends({ ignoreConstraints: false, ignoreSearch: false }, opts); | ||
var encodedParams = Object.keys(params).reduce(function (acc, key) { | ||
// Use encodeURI in case of spats | ||
if (params[key] === undefined) { | ||
acc[key] = undefined; | ||
if (!exists(params[key])) { | ||
return acc; | ||
} | ||
var val = params[key]; | ||
var encode = _this4._isSpatParam(key) ? encodeURI : encodeURIComponent; | ||
if (typeof val === 'boolean') { | ||
acc[key] = val; | ||
} else if (Array.isArray(val)) { | ||
acc[key] = val.map(encode); | ||
} else { | ||
acc[key] = Array.isArray(params[key]) ? params[key].map(encodeURI) : encodeURI(params[key]); | ||
acc[key] = encode(val); | ||
} | ||
return acc; | ||
}, {}); | ||
// Check all params are provided (not search parameters which are optional) | ||
if (this.urlParams.some(function (p) { | ||
return params[p] === undefined; | ||
return !exists(encodedParams[p]); | ||
})) throw new Error('Missing parameters'); | ||
// Check constraints | ||
if (!opts.ignoreConstraints) { | ||
if (!options.ignoreConstraints) { | ||
var constraintsPassed = this.tokens.filter(function (t) { | ||
@@ -337,3 +351,3 @@ return (/^url-parameter/.test(t.type) && !/-splat$/.test(t.type) | ||
if (opts.ignoreSearch) return base; | ||
if (options.ignoreSearch) return base; | ||
@@ -345,5 +359,5 @@ var queryParams = this.queryParams.concat(this.queryParamsBr.map(function (p) { | ||
var searchPart = queryParams.filter(function (p) { | ||
return Object.keys(encodedParams).indexOf(withoutBrackets(p)) !== -1; | ||
return Object.keys(encodedParams).indexOf((0, _searchParams.withoutBrackets)(p)) !== -1; | ||
}).map(function (p) { | ||
return _serialise(p, encodedParams[withoutBrackets(p)]); | ||
return _serialise(p, encodedParams[(0, _searchParams.withoutBrackets)(p)]); | ||
}).join('&'); | ||
@@ -356,5 +370,5 @@ | ||
return Path; | ||
})(); | ||
}(); | ||
exports.default = Path; | ||
module.exports = exports['default']; |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define('Path', factory) : | ||
(global.Path = factory()); | ||
}(this, function () { 'use strict'; | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('search-params')) : | ||
typeof define === 'function' && define.amd ? define('Path', ['search-params'], factory) : | ||
(global.Path = factory(global.searchParams)); | ||
}(this, (function (searchParams) { 'use strict'; | ||
var babelHelpers = {}; | ||
var asyncGenerator = function () { | ||
function AwaitValue(value) { | ||
this.value = value; | ||
} | ||
function babelHelpers_classCallCheck (instance, Constructor) { | ||
if (!(instance instanceof Constructor)) { | ||
throw new TypeError("Cannot call a class as a function"); | ||
} | ||
}; | ||
function AsyncGenerator(gen) { | ||
var front, back; | ||
var babelHelpers_createClass = (function () { | ||
function defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
function send(key, arg) { | ||
return new Promise(function (resolve, reject) { | ||
var request = { | ||
key: key, | ||
arg: arg, | ||
resolve: resolve, | ||
reject: reject, | ||
next: null | ||
}; | ||
if (back) { | ||
back = back.next = request; | ||
} else { | ||
front = back = request; | ||
resume(key, arg); | ||
} | ||
}); | ||
} | ||
function resume(key, arg) { | ||
try { | ||
var result = gen[key](arg); | ||
var value = result.value; | ||
if (value instanceof AwaitValue) { | ||
Promise.resolve(value.value).then(function (arg) { | ||
resume("next", arg); | ||
}, function (arg) { | ||
resume("throw", arg); | ||
}); | ||
} else { | ||
settle(result.done ? "return" : "normal", result.value); | ||
} | ||
} catch (err) { | ||
settle("throw", err); | ||
} | ||
} | ||
return function (Constructor, protoProps, staticProps) { | ||
if (protoProps) defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
}; | ||
})(); | ||
function settle(type, value) { | ||
switch (type) { | ||
case "return": | ||
front.resolve({ | ||
value: value, | ||
done: true | ||
}); | ||
break; | ||
var defaultOrConstrained = function defaultOrConstrained(match) { | ||
return '(' + (match ? match.replace(/(^<|>$)/g, '') : '[a-zA-Z0-9-_.~%]+') + ')'; | ||
case "throw": | ||
front.reject(value); | ||
break; | ||
default: | ||
front.resolve({ | ||
value: value, | ||
done: false | ||
}); | ||
break; | ||
} | ||
front = front.next; | ||
if (front) { | ||
resume(front.key, front.arg); | ||
} else { | ||
back = null; | ||
} | ||
} | ||
this._invoke = send; | ||
if (typeof gen.return !== "function") { | ||
this.return = undefined; | ||
} | ||
} | ||
if (typeof Symbol === "function" && Symbol.asyncIterator) { | ||
AsyncGenerator.prototype[Symbol.asyncIterator] = function () { | ||
return this; | ||
}; | ||
} | ||
var rules = [{ | ||
// An URL can contain a parameter :paramName | ||
// - and _ are allowed but not in last position | ||
name: 'url-parameter', | ||
pattern: /^:([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Url parameter (splat) | ||
name: 'url-parameter-splat', | ||
pattern: /^\*([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/, | ||
regex: /([^\?]*)/ | ||
}, { | ||
name: 'url-parameter-matrix', | ||
pattern: /^\;([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(';' + match[1] + '=' + defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter-bracket', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(?:\[\])/ | ||
}, // regex: match => new RegExp('(?=(\?|.*&)' + match[0] + '(?=(\=|&|$)))') | ||
{ | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/ | ||
}, // regex: match => new RegExp('(?=(\?|.*&)' + match[0] + '(?=(\=|&|$)))') | ||
{ | ||
// Delimiter / | ||
name: 'delimiter', | ||
pattern: /^(\/|\?)/, | ||
regex: function regex(match) { | ||
return new RegExp('\\' + match[0]); | ||
} | ||
}, { | ||
// Sub delimiters | ||
name: 'sub-delimiter', | ||
pattern: /^(\!|\&|\-|_|\.|;)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}, { | ||
// Unmatched fragment (until delimiter is found) | ||
name: 'fragment', | ||
pattern: /^([0-9a-zA-Z]+)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}]; | ||
AsyncGenerator.prototype.next = function (arg) { | ||
return this._invoke("next", arg); | ||
}; | ||
var tokenise = function tokenise(str) { | ||
var tokens = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; | ||
AsyncGenerator.prototype.throw = function (arg) { | ||
return this._invoke("throw", arg); | ||
}; | ||
// Look for a matching rule | ||
var matched = rules.some(function (rule) { | ||
var match = str.match(rule.pattern); | ||
if (!match) return false; | ||
AsyncGenerator.prototype.return = function (arg) { | ||
return this._invoke("return", arg); | ||
}; | ||
tokens.push({ | ||
type: rule.name, | ||
match: match[0], | ||
val: match.slice(1, 2), | ||
otherVal: match.slice(2), | ||
regex: rule.regex instanceof Function ? rule.regex(match) : rule.regex | ||
}); | ||
return { | ||
wrap: function (fn) { | ||
return function () { | ||
return new AsyncGenerator(fn.apply(this, arguments)); | ||
}; | ||
}, | ||
await: function (value) { | ||
return new AwaitValue(value); | ||
} | ||
}; | ||
}(); | ||
if (match[0].length < str.length) tokens = tokenise(str.substr(match[0].length), tokens); | ||
return true; | ||
var classCallCheck = function (instance, Constructor) { | ||
if (!(instance instanceof Constructor)) { | ||
throw new TypeError("Cannot call a class as a function"); | ||
} | ||
}; | ||
var createClass = function () { | ||
function defineProperties(target, props) { | ||
for (var i = 0; i < props.length; i++) { | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
return function (Constructor, protoProps, staticProps) { | ||
if (protoProps) defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
}; | ||
}(); | ||
var _extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
var get = function get(object, property, receiver) { | ||
if (object === null) object = Function.prototype; | ||
var desc = Object.getOwnPropertyDescriptor(object, property); | ||
if (desc === undefined) { | ||
var parent = Object.getPrototypeOf(object); | ||
if (parent === null) { | ||
return undefined; | ||
} else { | ||
return get(parent, property, receiver); | ||
} | ||
} else if ("value" in desc) { | ||
return desc.value; | ||
} else { | ||
var getter = desc.get; | ||
if (getter === undefined) { | ||
return undefined; | ||
} | ||
return getter.call(receiver); | ||
} | ||
}; | ||
var set = function set(object, property, value, receiver) { | ||
var desc = Object.getOwnPropertyDescriptor(object, property); | ||
if (desc === undefined) { | ||
var parent = Object.getPrototypeOf(object); | ||
if (parent !== null) { | ||
set(parent, property, value, receiver); | ||
} | ||
} else if ("value" in desc && desc.writable) { | ||
desc.value = value; | ||
} else { | ||
var setter = desc.set; | ||
if (setter !== undefined) { | ||
setter.call(receiver, value); | ||
} | ||
} | ||
return value; | ||
}; | ||
var defaultOrConstrained = function defaultOrConstrained(match) { | ||
return '(' + (match ? match.replace(/(^<|>$)/g, '') : '[a-zA-Z0-9-_.~%]+') + ')'; | ||
}; | ||
var rules = [{ | ||
// An URL can contain a parameter :paramName | ||
// - and _ are allowed but not in last position | ||
name: 'url-parameter', | ||
pattern: /^:([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Url parameter (splat) | ||
name: 'url-parameter-splat', | ||
pattern: /^\*([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/, | ||
regex: /([^\?]*)/ | ||
}, { | ||
name: 'url-parameter-matrix', | ||
pattern: /^\;([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(<(.+?)>)?/, | ||
regex: function regex(match) { | ||
return new RegExp(';' + match[1] + '=' + defaultOrConstrained(match[2])); | ||
} | ||
}, { | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter-bracket', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})(?:\[\])/ | ||
}, { | ||
// Query parameter: ?param1¶m2 | ||
// ?:param1&:param2 | ||
name: 'query-parameter', | ||
pattern: /^(?:\?|&)(?:\:)?([a-zA-Z0-9-_]*[a-zA-Z0-9]{1})/ | ||
}, { | ||
// Delimiter / | ||
name: 'delimiter', | ||
pattern: /^(\/|\?)/, | ||
regex: function regex(match) { | ||
return new RegExp('\\' + match[0]); | ||
} | ||
}, { | ||
// Sub delimiters | ||
name: 'sub-delimiter', | ||
pattern: /^(\!|\&|\-|_|\.|;)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}, { | ||
// Unmatched fragment (until delimiter is found) | ||
name: 'fragment', | ||
pattern: /^([0-9a-zA-Z]+)/, | ||
regex: function regex(match) { | ||
return new RegExp(match[0]); | ||
} | ||
}]; | ||
var exists = function exists(val) { | ||
return val !== undefined && val !== null; | ||
}; | ||
var tokenise = function tokenise(str) { | ||
var tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
// Look for a matching rule | ||
var matched = rules.some(function (rule) { | ||
var match = str.match(rule.pattern); | ||
if (!match) return false; | ||
tokens.push({ | ||
type: rule.name, | ||
match: match[0], | ||
val: match.slice(1, 2), | ||
otherVal: match.slice(2), | ||
regex: rule.regex instanceof Function ? rule.regex(match) : rule.regex | ||
}); | ||
// If no rules matched, throw an error (possible malformed path) | ||
if (!matched) { | ||
throw new Error('Could not parse path.'); | ||
} | ||
// Return tokens | ||
return tokens; | ||
}; | ||
if (match[0].length < str.length) tokens = tokenise(str.substr(match[0].length), tokens); | ||
return true; | ||
}); | ||
var optTrailingSlash = function optTrailingSlash(source, trailingSlash) { | ||
if (!trailingSlash) return source; | ||
return source.replace(/\\\/$/, '') + '(?:\\/)?'; | ||
}; | ||
// If no rules matched, throw an error (possible malformed path) | ||
if (!matched) { | ||
throw new Error('Could not parse path.'); | ||
} | ||
// Return tokens | ||
return tokens; | ||
}; | ||
var withoutBrackets = function withoutBrackets(param) { | ||
return param.replace(/\[\]$/, ''); | ||
}; | ||
var optTrailingSlash = function optTrailingSlash(source, trailingSlash) { | ||
if (!trailingSlash) return source; | ||
return source.replace(/\\\/$/, '') + '(?:\\/)?'; | ||
}; | ||
var appendQueryParam = function appendQueryParam(params, param) { | ||
var val = arguments.length <= 2 || arguments[2] === undefined ? '' : arguments[2]; | ||
var upToDelimiter = function upToDelimiter(source, delimiter) { | ||
if (!delimiter) return source; | ||
return source.replace(/\\(\/|\?|\.|;)$/, '') + '(\\/|\\?|\\.|;|$)'; | ||
}; | ||
if (/\[\]$/.test(param)) { | ||
param = withoutBrackets(param); | ||
val = [val]; | ||
} | ||
var existingVal = params[param]; | ||
var appendQueryParam = function appendQueryParam(params, param) { | ||
var val = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; | ||
if (existingVal === undefined) params[param] = val;else params[param] = Array.isArray(existingVal) ? existingVal.concat(val) : [existingVal, val]; | ||
if (/\[\]$/.test(param)) { | ||
param = searchParams.withoutBrackets(param); | ||
val = [val]; | ||
} | ||
var existingVal = params[param]; | ||
return params; | ||
}; | ||
if (existingVal === undefined) params[param] = val;else params[param] = Array.isArray(existingVal) ? existingVal.concat(val) : [existingVal, val]; | ||
var parseQueryParams = function parseQueryParams(path) { | ||
var searchPart = path.split('?')[1]; | ||
if (!searchPart) return {}; | ||
return params; | ||
}; | ||
return searchPart.split('&').map(function (_) { | ||
return _.split('='); | ||
}).reduce(function (obj, m) { | ||
return appendQueryParam(obj, m[0], m[1] ? decodeURIComponent(m[1]) : m[1]); | ||
}, {}); | ||
}; | ||
var parseQueryParams = function parseQueryParams(path) { | ||
var searchPart = searchParams.getSearch(path); | ||
if (!searchPart) return {}; | ||
var toSerialisable = function toSerialisable(val) { | ||
return val !== undefined && val !== null && val !== '' ? '=' + val : ''; | ||
}; | ||
return searchParams.toObject(searchParams.parse(searchPart)); | ||
}; | ||
var _serialise = function _serialise(key, val) { | ||
return Array.isArray(val) ? val.map(function (v) { | ||
function _serialise(key, val) { | ||
if (Array.isArray(val)) { | ||
return val.map(function (v) { | ||
return _serialise(key, v); | ||
}).join('&') : key + toSerialisable(val); | ||
}; | ||
}).join('&'); | ||
} | ||
var Path = (function () { | ||
babelHelpers_createClass(Path, null, [{ | ||
key: 'createPath', | ||
value: function createPath(path) { | ||
return new Path(path); | ||
} | ||
}, { | ||
key: 'serialise', | ||
value: function serialise(key, val) { | ||
return _serialise(key, val); | ||
} | ||
}]); | ||
if (val === true) { | ||
return key; | ||
} | ||
function Path(path) { | ||
babelHelpers_classCallCheck(this, Path); | ||
return key + '=' + val; | ||
} | ||
if (!path) throw new Error('Please supply a path'); | ||
this.path = path; | ||
this.tokens = tokenise(path); | ||
var Path = function () { | ||
createClass(Path, null, [{ | ||
key: 'createPath', | ||
value: function createPath(path) { | ||
return new Path(path); | ||
} | ||
}, { | ||
key: 'serialise', | ||
value: function serialise(key, val) { | ||
return _serialise(key, val); | ||
} | ||
}]); | ||
this.hasUrlParams = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasSpatParam = this.tokens.filter(function (t) { | ||
return (/splat$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasMatrixParams = this.tokens.filter(function (t) { | ||
return (/matrix$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasQueryParams = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
// Extract named parameters from tokens | ||
this.urlParams = !this.hasUrlParams ? [] : this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
return t.val.slice(0, 1); | ||
}) | ||
// Flatten | ||
.reduce(function (r, v) { | ||
return r.concat(v); | ||
}); | ||
// Query params | ||
this.queryParams = !this.hasQueryParams ? [] : this.tokens.filter(function (t) { | ||
return t.type === 'query-parameter'; | ||
}).map(function (t) { | ||
return t.val; | ||
}).reduce(function (r, v) { | ||
return r.concat(v); | ||
}, []); | ||
function Path(path) { | ||
classCallCheck(this, Path); | ||
this.queryParamsBr = !this.hasQueryParams ? [] : this.tokens.filter(function (t) { | ||
return (/-bracket$/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
return t.val; | ||
}).reduce(function (r, v) { | ||
return r.concat(v); | ||
}, []); | ||
if (!path) throw new Error('Please supply a path'); | ||
this.path = path; | ||
this.tokens = tokenise(path); | ||
this.params = this.urlParams.concat(this.queryParams).concat(this.queryParamsBr); | ||
// Check if hasQueryParams | ||
// Regular expressions for url part only (full and partial match) | ||
this.source = this.tokens.filter(function (t) { | ||
return t.regex !== undefined; | ||
}).map(function (r) { | ||
return r.regex.source; | ||
}).join(''); | ||
this.hasUrlParams = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasSpatParam = this.tokens.filter(function (t) { | ||
return (/splat$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasMatrixParams = this.tokens.filter(function (t) { | ||
return (/matrix$/.test(t.type) | ||
); | ||
}).length > 0; | ||
this.hasQueryParams = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) | ||
); | ||
}).length > 0; | ||
// Extract named parameters from tokens | ||
this.spatParams = this._getParams('url-parameter-splat'); | ||
this.urlParams = this._getParams(/^url-parameter/); | ||
// Query params | ||
this.queryParams = this._getParams('query-parameter'); | ||
this.queryParamsBr = this._getParams('query-parameter-bracket'); | ||
// All params | ||
this.params = this.urlParams.concat(this.queryParams).concat(this.queryParamsBr); | ||
// Check if hasQueryParams | ||
// Regular expressions for url part only (full and partial match) | ||
this.source = this.tokens.filter(function (t) { | ||
return t.regex !== undefined; | ||
}).map(function (r) { | ||
return r.regex.source; | ||
}).join(''); | ||
} | ||
createClass(Path, [{ | ||
key: '_getParams', | ||
value: function _getParams(type) { | ||
var predicate = type instanceof RegExp ? function (t) { | ||
return type.test(t.type); | ||
} : function (t) { | ||
return t.type === type; | ||
}; | ||
return this.tokens.filter(predicate).map(function (t) { | ||
return t.val[0]; | ||
}); | ||
} | ||
}, { | ||
key: '_isSpatParam', | ||
value: function _isSpatParam(name) { | ||
return this.spatParams.indexOf(name) !== -1; | ||
} | ||
}, { | ||
key: '_urlTest', | ||
value: function _urlTest(path, regex) { | ||
var _this = this; | ||
babelHelpers_createClass(Path, [{ | ||
key: '_urlMatch', | ||
value: function _urlMatch(path, regex) { | ||
var _this = this; | ||
var match = path.match(regex); | ||
if (!match) return null;else if (!this.urlParams.length) return {}; | ||
// Reduce named params to key-value pairs | ||
return match.slice(1, this.urlParams.length + 1).reduce(function (params, m, i) { | ||
params[_this.urlParams[i]] = decodeURIComponent(m); | ||
return params; | ||
}, {}); | ||
} | ||
}, { | ||
key: 'test', | ||
value: function test(path, opts) { | ||
var _this2 = this; | ||
var match = path.match(regex); | ||
if (!match) return null;else if (!this.urlParams.length) return {}; | ||
// Reduce named params to key-value pairs | ||
return match.slice(1, this.urlParams.length + 1).reduce(function (params, m, i) { | ||
params[_this.urlParams[i]] = decodeURIComponent(m); | ||
return params; | ||
}, {}); | ||
} | ||
}, { | ||
key: 'match', | ||
value: function match(path) { | ||
var _this2 = this; | ||
var options = _extends({ trailingSlash: false }, opts); | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, options.trailingSlash); | ||
// Check if exact match | ||
var matched = this._urlTest(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
// If no match, or no query params, no need to go further | ||
if (!matched || !this.hasQueryParams) return matched; | ||
// Extract query params | ||
var queryParams = parseQueryParams(path); | ||
var unexpectedQueryParams = Object.keys(queryParams).filter(function (p) { | ||
return _this2.queryParams.concat(_this2.queryParamsBr).indexOf(p) === -1; | ||
}); | ||
var trailingSlash = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, trailingSlash); | ||
// Check if exact match | ||
var matched = this._urlMatch(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
// If no match, or no query params, no need to go further | ||
if (!matched || !this.hasQueryParams) return matched; | ||
// Extract query params | ||
var queryParams = parseQueryParams(path); | ||
var unexpectedQueryParams = Object.keys(queryParams).filter(function (p) { | ||
return _this2.queryParams.concat(_this2.queryParamsBr).indexOf(p) === -1; | ||
if (unexpectedQueryParams.length === 0) { | ||
// Extend url match | ||
Object.keys(queryParams).forEach(function (p) { | ||
return matched[p] = queryParams[p]; | ||
}); | ||
if (unexpectedQueryParams.length === 0) { | ||
// Extend url match | ||
Object.keys(queryParams).forEach(function (p) { | ||
return matched[p] = queryParams[p]; | ||
}); | ||
return matched; | ||
} | ||
return matched; | ||
} | ||
return null; | ||
} | ||
}, { | ||
key: 'partialTest', | ||
value: function partialTest(path, opts) { | ||
var _this3 = this; | ||
return null; | ||
} | ||
}, { | ||
key: 'partialMatch', | ||
value: function partialMatch(path) { | ||
var _this3 = this; | ||
var options = _extends({ delimited: true }, opts); | ||
// Check if partial match (start of given path matches regex) | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = upToDelimiter(this.source, options.delimited); | ||
var match = this._urlTest(path, new RegExp('^' + source)); | ||
var trailingSlash = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; | ||
if (!match) return match; | ||
// Check if partial match (start of given path matches regex) | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
var source = optTrailingSlash(this.source, trailingSlash); | ||
var match = this._urlMatch(path, new RegExp('^' + source)); | ||
if (!this.hasQueryParams) return match; | ||
if (!match) return match; | ||
var queryParams = parseQueryParams(path); | ||
if (!this.hasQueryParams) return match; | ||
Object.keys(queryParams).filter(function (p) { | ||
return _this3.queryParams.concat(_this3.queryParamsBr).indexOf(p) >= 0; | ||
}).forEach(function (p) { | ||
return appendQueryParam(match, p, queryParams[p]); | ||
}); | ||
var queryParams = parseQueryParams(path); | ||
return match; | ||
} | ||
}, { | ||
key: 'build', | ||
value: function build() { | ||
var _this4 = this; | ||
Object.keys(queryParams).filter(function (p) { | ||
return _this3.queryParams.concat(_this3.queryParamsBr).indexOf(p) >= 0; | ||
}).forEach(function (p) { | ||
return appendQueryParam(match, p, queryParams[p]); | ||
}); | ||
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return match; | ||
} | ||
}, { | ||
key: 'build', | ||
value: function build() { | ||
var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? { ignoreConstraints: false, ignoreSearch: false } : arguments[1]; | ||
var encodedParams = Object.keys(params).reduce(function (acc, key) { | ||
// Use encodeURI in case of spats | ||
if (params[key] === undefined) { | ||
acc[key] = undefined; | ||
} else { | ||
acc[key] = Array.isArray(params[key]) ? params[key].map(encodeURI) : encodeURI(params[key]); | ||
} | ||
var options = _extends({ ignoreConstraints: false, ignoreSearch: false }, opts); | ||
var encodedParams = Object.keys(params).reduce(function (acc, key) { | ||
if (!exists(params[key])) { | ||
return acc; | ||
}, {}); | ||
// Check all params are provided (not search parameters which are optional) | ||
if (this.urlParams.some(function (p) { | ||
return params[p] === undefined; | ||
})) throw new Error('Missing parameters'); | ||
} | ||
// Check constraints | ||
if (!opts.ignoreConstraints) { | ||
var constraintsPassed = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) && !/-splat$/.test(t.type) | ||
); | ||
}).every(function (t) { | ||
return new RegExp('^' + defaultOrConstrained(t.otherVal[0]) + '$').test(encodedParams[t.val]); | ||
}); | ||
var val = params[key]; | ||
var encode = _this4._isSpatParam(key) ? encodeURI : encodeURIComponent; | ||
if (!constraintsPassed) throw new Error('Some parameters are of invalid format'); | ||
if (typeof val === 'boolean') { | ||
acc[key] = val; | ||
} else if (Array.isArray(val)) { | ||
acc[key] = val.map(encode); | ||
} else { | ||
acc[key] = encode(val); | ||
} | ||
var base = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) === false | ||
return acc; | ||
}, {}); | ||
// Check all params are provided (not search parameters which are optional) | ||
if (this.urlParams.some(function (p) { | ||
return !exists(encodedParams[p]); | ||
})) throw new Error('Missing parameters'); | ||
// Check constraints | ||
if (!options.ignoreConstraints) { | ||
var constraintsPassed = this.tokens.filter(function (t) { | ||
return (/^url-parameter/.test(t.type) && !/-splat$/.test(t.type) | ||
); | ||
}).map(function (t) { | ||
if (t.type === 'url-parameter-matrix') return ';' + t.val + '=' + encodedParams[t.val[0]]; | ||
return (/^url-parameter/.test(t.type) ? encodedParams[t.val[0]] : t.match | ||
); | ||
}).join(''); | ||
}).every(function (t) { | ||
return new RegExp('^' + defaultOrConstrained(t.otherVal[0]) + '$').test(encodedParams[t.val]); | ||
}); | ||
if (opts.ignoreSearch) return base; | ||
if (!constraintsPassed) throw new Error('Some parameters are of invalid format'); | ||
} | ||
var queryParams = this.queryParams.concat(this.queryParamsBr.map(function (p) { | ||
return p + '[]'; | ||
})); | ||
var base = this.tokens.filter(function (t) { | ||
return (/^query-parameter/.test(t.type) === false | ||
); | ||
}).map(function (t) { | ||
if (t.type === 'url-parameter-matrix') return ';' + t.val + '=' + encodedParams[t.val[0]]; | ||
return (/^url-parameter/.test(t.type) ? encodedParams[t.val[0]] : t.match | ||
); | ||
}).join(''); | ||
var searchPart = queryParams.filter(function (p) { | ||
return Object.keys(encodedParams).indexOf(withoutBrackets(p)) !== -1; | ||
}).map(function (p) { | ||
return _serialise(p, encodedParams[withoutBrackets(p)]); | ||
}).join('&'); | ||
if (options.ignoreSearch) return base; | ||
return base + (searchPart ? '?' + searchPart : ''); | ||
} | ||
}]); | ||
return Path; | ||
})(); | ||
var queryParams = this.queryParams.concat(this.queryParamsBr.map(function (p) { | ||
return p + '[]'; | ||
})); | ||
var searchPart = queryParams.filter(function (p) { | ||
return Object.keys(encodedParams).indexOf(searchParams.withoutBrackets(p)) !== -1; | ||
}).map(function (p) { | ||
return _serialise(p, encodedParams[searchParams.withoutBrackets(p)]); | ||
}).join('&'); | ||
return base + (searchPart ? '?' + searchPart : ''); | ||
} | ||
}]); | ||
return Path; | ||
}(); | ||
})); | ||
return Path; | ||
}))); |
@@ -0,1 +1,3 @@ | ||
import { getSearch, withoutBrackets, parse, toObject } from 'search-params'; | ||
const defaultOrConstrained = (match) => | ||
@@ -57,2 +59,4 @@ '(' + (match ? match.replace(/(^<|>$)/g, '') : '[a-zA-Z0-9-_.~%]+') + ')'; | ||
const exists = (val) => val !== undefined && val !== null; | ||
const tokenise = (str, tokens = []) => { | ||
@@ -90,3 +94,6 @@ // Look for a matching rule | ||
const withoutBrackets = param => param.replace(/\[\]$/, ''); | ||
const upToDelimiter = (source, delimiter) => { | ||
if (!delimiter) return source; | ||
return source.replace(/\\(\/|\?|\.|;)$/, '') + '(\\/|\\?|\\.|;|$)'; | ||
}; | ||
@@ -107,15 +114,20 @@ const appendQueryParam = (params, param, val = '') => { | ||
const parseQueryParams = path => { | ||
let searchPart = path.split('?')[1]; | ||
let searchPart = getSearch(path); | ||
if (!searchPart) return {}; | ||
return searchPart | ||
.split('&') | ||
.map(_ => _.split('=')) | ||
.reduce((obj, m) => appendQueryParam(obj, m[0], m[1] ? decodeURIComponent(m[1]) : m[1]), {}); | ||
return toObject(parse(searchPart)); | ||
}; | ||
const toSerialisable = val => val !== undefined && val !== null && val !== '' ? `=${val}` : ''; | ||
function serialise(key, val) { | ||
if (Array.isArray(val)) { | ||
return val.map(v => serialise(key, v)).join('&'); | ||
} | ||
const serialise = (key, val) => Array.isArray(val) ? val.map(v => serialise(key, v)).join('&') : key + toSerialisable(val); | ||
if (val === true) { | ||
return key; | ||
} | ||
return `${key}=${val}`; | ||
} | ||
export default class Path { | ||
@@ -140,18 +152,8 @@ static createPath(path) { | ||
// Extract named parameters from tokens | ||
this.urlParams = !this.hasUrlParams ? [] : this.tokens | ||
.filter(t => /^url-parameter/.test(t.type)) | ||
.map(t => t.val.slice(0, 1)) | ||
// Flatten | ||
.reduce((r, v) => r.concat(v)); | ||
this.spatParams = this._getParams('url-parameter-splat'); | ||
this.urlParams = this._getParams(/^url-parameter/); | ||
// Query params | ||
this.queryParams = !this.hasQueryParams ? [] : this.tokens | ||
.filter(t => t.type === 'query-parameter') | ||
.map(t => t.val) | ||
.reduce((r, v) => r.concat(v), []); | ||
this.queryParamsBr = !this.hasQueryParams ? [] : this.tokens | ||
.filter(t => /-bracket$/.test(t.type)) | ||
.map(t => t.val) | ||
.reduce((r, v) => r.concat(v), []); | ||
this.queryParams = this._getParams('query-parameter'); | ||
this.queryParamsBr = this._getParams('query-parameter-bracket'); | ||
// All params | ||
this.params = this.urlParams.concat(this.queryParams).concat(this.queryParamsBr); | ||
@@ -166,3 +168,17 @@ // Check if hasQueryParams | ||
_urlMatch(path, regex) { | ||
_getParams(type) { | ||
const predicate = type instanceof RegExp | ||
? t => type.test(t.type) | ||
: t => t.type === type; | ||
return this.tokens | ||
.filter(predicate) | ||
.map(t => t.val[0]); | ||
} | ||
_isSpatParam(name) { | ||
return this.spatParams.indexOf(name) !== -1; | ||
} | ||
_urlTest(path, regex) { | ||
let match = path.match(regex); | ||
@@ -179,7 +195,8 @@ if (!match) return null; | ||
match(path, trailingSlash = 0) { | ||
test(path, opts) { | ||
const options = { trailingSlash: false, ...opts }; | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
const source = optTrailingSlash(this.source, trailingSlash); | ||
const source = optTrailingSlash(this.source, options.trailingSlash); | ||
// Check if exact match | ||
const matched = this._urlMatch(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
const matched = this._urlTest(path, new RegExp('^' + source + (this.hasQueryParams ? '(\\?.*$|$)' : '$'))); | ||
// If no match, or no query params, no need to go further | ||
@@ -203,7 +220,8 @@ if (!matched || !this.hasQueryParams) return matched; | ||
partialMatch(path, trailingSlash = 0) { | ||
partialTest(path, opts) { | ||
const options = { delimited: true, ...opts }; | ||
// Check if partial match (start of given path matches regex) | ||
// trailingSlash: falsy => non optional, truthy => optional | ||
let source = optTrailingSlash(this.source, trailingSlash); | ||
let match = this._urlMatch(path, new RegExp('^' + source)); | ||
let source = upToDelimiter(this.source, options.delimited); | ||
let match = this._urlTest(path, new RegExp('^' + source)); | ||
@@ -223,13 +241,21 @@ if (!match) return match; | ||
build(params = {}, opts = {ignoreConstraints: false, ignoreSearch: false}) { | ||
build(params = {}, opts = {}) { | ||
const options = { ignoreConstraints: false, ignoreSearch: false, ...opts }; | ||
const encodedParams = Object.keys(params).reduce( | ||
(acc, key) => { | ||
// Use encodeURI in case of spats | ||
if (params[key] === undefined) { | ||
acc[key] = undefined; | ||
if (!exists(params[key])) { | ||
return acc; | ||
} | ||
const val = params[key]; | ||
const encode = this._isSpatParam(key) ? encodeURI : encodeURIComponent; | ||
if (typeof val === 'boolean') { | ||
acc[key] = val; | ||
} else if (Array.isArray(val)) { | ||
acc[key] = val.map(encode); | ||
} else { | ||
acc[key] = Array.isArray(params[key]) | ||
? params[key].map(encodeURI) | ||
: encodeURI(params[key]); | ||
acc[key] = encode(val); | ||
} | ||
return acc; | ||
@@ -239,7 +265,8 @@ }, | ||
); | ||
// Check all params are provided (not search parameters which are optional) | ||
if (this.urlParams.some(p => params[p] === undefined)) throw new Error('Missing parameters'); | ||
if (this.urlParams.some(p => !exists(encodedParams[p]))) throw new Error('Missing parameters'); | ||
// Check constraints | ||
if (!opts.ignoreConstraints) { | ||
if (!options.ignoreConstraints) { | ||
let constraintsPassed = this.tokens | ||
@@ -260,3 +287,3 @@ .filter(t => /^url-parameter/.test(t.type) && !/-splat$/.test(t.type)) | ||
if (opts.ignoreSearch) return base; | ||
if (options.ignoreSearch) return base; | ||
@@ -263,0 +290,0 @@ const queryParams = this.queryParams.concat(this.queryParamsBr.map(p => p + '[]')); |
{ | ||
"name": "path-parser", | ||
"version": "1.0.4", | ||
"version": "2.0.0", | ||
"description": "A small utility to parse, match and generate paths", | ||
@@ -38,22 +38,25 @@ "main": "dist/commonjs/path-parser.js", | ||
"devDependencies": { | ||
"babel-core": "^6.3.26", | ||
"babel-eslint": "^4.1.6", | ||
"babel-plugin-add-module-exports": "^0.1.2", | ||
"babel-plugin-transform-class-properties": "^6.3.13", | ||
"babel-plugin-transform-export-extensions": "^6.3.13", | ||
"babel-plugin-transform-object-rest-spread": "^6.3.13", | ||
"babel-preset-es2015": "^6.3.13", | ||
"babel-preset-es2015-rollup": "^1.0.0", | ||
"conventional-changelog": "^0.5.3", | ||
"coveralls": "^2.11.2", | ||
"eslint": "^1.10.3", | ||
"babel-core": "~6.9.0", | ||
"babel-eslint": "~7.0.0", | ||
"babel-plugin-add-module-exports": "~0.2.1", | ||
"babel-plugin-transform-class-properties": "~6.9.1", | ||
"babel-plugin-transform-export-extensions": "~6.8.0", | ||
"babel-plugin-transform-object-rest-spread": "~6.8.0", | ||
"babel-preset-es2015": "~6.9.0", | ||
"babel-preset-es2015-rollup": "~1.2.0", | ||
"conventional-changelog": "~1.1.0", | ||
"coveralls": "~2.11.14", | ||
"eslint": "~3.7.1", | ||
"isparta": "~4.0.0", | ||
"mkdirp": "^0.5.1", | ||
"mocha": "^2.2.5", | ||
"rimraf": "^2.5.0", | ||
"rollup": "^0.24.0", | ||
"rollup-plugin-babel": "^2.3.5", | ||
"should": "^6.0.3", | ||
"yargs": "^3.31.0" | ||
"mkdirp": "~0.5.1", | ||
"mocha": "~3.1.2", | ||
"rimraf": "~2.5.4", | ||
"rollup": "~0.36.3", | ||
"rollup-plugin-babel": "~2.6.1", | ||
"should": "~11.1.1", | ||
"yargs": "~6.0.0" | ||
}, | ||
"dependencies": { | ||
"search-params": "~1.3.0" | ||
} | ||
} |
@@ -8,5 +8,5 @@ [![npm version](https://badge.fury.io/js/path-parser.svg)](http://badge.fury.io/js/path-parser) | ||
A small utility to parse and build paths. It can be used to partially or fully | ||
match paths against a defined pattern. | ||
test paths against a defined pattern. | ||
Partial match allows to determine if a given path starts with the defined pattern. | ||
Partial testing allows to determine if a given path starts with the defined pattern. | ||
It is used by [route-node](https://github.com/troch/route-node) | ||
@@ -21,7 +21,7 @@ | ||
// Matching | ||
p.match('/users/profile/00123') // => {id: "00123"} | ||
// Partial matching: does this path | ||
p.test('/users/profile/00123') // => {id: "00123"} | ||
// Partial testing: does this path | ||
// starts with that pattern? | ||
p.partialMatch('/users/profile/00123/orders') // => {id: "00123"} | ||
p.partialMatch('/profile/00123/orders') // => null | ||
p.partialTest('/users/profile/00123/orders') // => {id: "00123"} | ||
p.partialTest('/profile/00123/orders') // => null | ||
// Building | ||
@@ -68,3 +68,4 @@ p.build({id: '00123'}) // => "users/profile/00123" | ||
When using `.match()` or `.partialMatch()`, you can path a second argument. If truthy, it will make trailing slashes optional. | ||
`.test(path, options)` accepts an option object: | ||
- `trailingSlash`: if truthy, it will make trailing slashes optional (default to `true`). | ||
@@ -74,6 +75,19 @@ ```javascript | ||
path.match('/my-path/') // => null | ||
path.match('/my-path/', true) // => {} | ||
path.test('/my-path/') // => null | ||
path.test('/my-path/', { trailingSlash: true }) // => {} | ||
``` | ||
## Partial test with delimiters | ||
`.partialTest(path, options)` accepts an option object: | ||
- `delimited`: if truthy, a partial test will only be successful if a delimiter is found at the end of a match (default to `true`, delimiters are `/`, `?`, `.` and `;`). | ||
```javascript | ||
var path = new Path('/my-path'); | ||
path.partialTest('/my-path/extended') // => {} | ||
path.partialTest('/my-path-extended') // => null | ||
path.partialTest('/my-path-extended', { delimited: false }) // => {} | ||
``` | ||
## Related modules | ||
@@ -80,0 +94,0 @@ |
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
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
234187
18
1891
93
1
+ Addedsearch-params@~1.3.0
+ Addedsearch-params@1.3.0(transitive)