'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;