|
|
|
'use strict';
|
|
|
|
|
|
|
|
var invariant = require('react/lib/invariant');
|
|
|
|
var assign = require('object-assign');
|
|
|
|
var qs = require('qs');
|
|
|
|
|
|
|
|
var paramCompileMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*.()\[\]\\+|{}^$]/g;
|
|
|
|
var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$?]*[?]?)|[*]/g;
|
|
|
|
var paramInjectTrailingSlashMatcher = /\/\/\?|\/\?\/|\/\?/g;
|
|
|
|
var queryMatcher = /\?(.*)$/;
|
|
|
|
|
|
|
|
var _compiledPatterns = {};
|
|
|
|
|
|
|
|
function compilePattern(pattern) {
|
|
|
|
if (!(pattern in _compiledPatterns)) {
|
|
|
|
var paramNames = [];
|
|
|
|
var source = pattern.replace(paramCompileMatcher, function (match, paramName) {
|
|
|
|
if (paramName) {
|
|
|
|
paramNames.push(paramName);
|
|
|
|
return '([^/?#]+)';
|
|
|
|
} else if (match === '*') {
|
|
|
|
paramNames.push('splat');
|
|
|
|
return '(.*?)';
|
|
|
|
} else {
|
|
|
|
return '\\' + match;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
_compiledPatterns[pattern] = {
|
|
|
|
matcher: new RegExp('^' + source + '$', 'i'),
|
|
|
|
paramNames: paramNames
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return _compiledPatterns[pattern];
|
|
|
|
}
|
|
|
|
|
|
|
|
var PathUtils = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the given path is absolute.
|
|
|
|
*/
|
|
|
|
isAbsolute: function isAbsolute(path) {
|
|
|
|
return path.charAt(0) === '/';
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Joins two URL paths together.
|
|
|
|
*/
|
|
|
|
join: function join(a, b) {
|
|
|
|
return a.replace(/\/*$/, '/') + b;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of the names of all parameters in the given pattern.
|
|
|
|
*/
|
|
|
|
extractParamNames: function extractParamNames(pattern) {
|
|
|
|
return compilePattern(pattern).paramNames;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extracts the portions of the given URL path that match the given pattern
|
|
|
|
* and returns an object of param name => value pairs. Returns null if the
|
|
|
|
* pattern does not match the given path.
|
|
|
|
*/
|
|
|
|
extractParams: function extractParams(pattern, path) {
|
|
|
|
var _compilePattern = compilePattern(pattern);
|
|
|
|
|
|
|
|
var matcher = _compilePattern.matcher;
|
|
|
|
var paramNames = _compilePattern.paramNames;
|
|
|
|
|
|
|
|
var match = path.match(matcher);
|
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
return null;
|
|
|
|
}var params = {};
|
|
|
|
|
|
|
|
paramNames.forEach(function (paramName, index) {
|
|
|
|
params[paramName] = match[index + 1];
|
|
|
|
});
|
|
|
|
|
|
|
|
return params;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a version of the given route path with params interpolated. Throws
|
|
|
|
* if there is a dynamic segment of the route path for which there is no param.
|
|
|
|
*/
|
|
|
|
injectParams: function injectParams(pattern, params) {
|
|
|
|
params = params || {};
|
|
|
|
|
|
|
|
var splatIndex = 0;
|
|
|
|
|
|
|
|
return pattern.replace(paramInjectMatcher, function (match, paramName) {
|
|
|
|
paramName = paramName || 'splat';
|
|
|
|
|
|
|
|
// If param is optional don't check for existence
|
|
|
|
if (paramName.slice(-1) === '?') {
|
|
|
|
paramName = paramName.slice(0, -1);
|
|
|
|
|
|
|
|
if (params[paramName] == null) return '';
|
|
|
|
} else {
|
|
|
|
invariant(params[paramName] != null, 'Missing "%s" parameter for path "%s"', paramName, pattern);
|
|
|
|
}
|
|
|
|
|
|
|
|
var segment;
|
|
|
|
if (paramName === 'splat' && Array.isArray(params[paramName])) {
|
|
|
|
segment = params[paramName][splatIndex++];
|
|
|
|
|
|
|
|
invariant(segment != null, 'Missing splat # %s for path "%s"', splatIndex, pattern);
|
|
|
|
} else {
|
|
|
|
segment = params[paramName];
|
|
|
|
}
|
|
|
|
|
|
|
|
return segment;
|
|
|
|
}).replace(paramInjectTrailingSlashMatcher, '/');
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an object that is the result of parsing any query string contained
|
|
|
|
* in the given path, null if the path contains no query string.
|
|
|
|
*/
|
|
|
|
extractQuery: function extractQuery(path) {
|
|
|
|
var match = path.match(queryMatcher);
|
|
|
|
return match && qs.parse(match[1]);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a version of the given path without the query string.
|
|
|
|
*/
|
|
|
|
withoutQuery: function withoutQuery(path) {
|
|
|
|
return path.replace(queryMatcher, '');
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a version of the given path with the parameters in the given
|
|
|
|
* query merged into the query string.
|
|
|
|
*/
|
|
|
|
withQuery: function withQuery(path, query) {
|
|
|
|
var existingQuery = PathUtils.extractQuery(path);
|
|
|
|
|
|
|
|
if (existingQuery) query = query ? assign(existingQuery, query) : existingQuery;
|
|
|
|
|
|
|
|
var queryString = qs.stringify(query, { arrayFormat: 'brackets' });
|
|
|
|
|
|
|
|
if (queryString) {
|
|
|
|
return PathUtils.withoutQuery(path) + '?' + queryString;
|
|
|
|
}return PathUtils.withoutQuery(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = PathUtils;
|