diff --git a/Makefile b/Makefile
index d76e478..dce6246 100644
--- a/Makefile
+++ b/Makefile
@@ -25,7 +25,7 @@ unittests: nodejsunittests browsersunittests
.PHONY: unittests_in_nodejs
nodejsunittests: $(MOCHA)
- $(MOCHA) -u bdd -R spec -r chai $(PRJ_DIR)test/lib/nodejs.js $(PRJ_DIR)test/*.js
+ $(MOCHA) -u bdd -R spec -r chai $(PRJ_DIR)test/nodejs $(PRJ_DIR)test
.PHONY: unittests_in_browsers
browsersunittests: $(KARMA) build
@@ -41,7 +41,7 @@ jscs: $(JSCS)
.PHONY: coverage
coverage: $(ISTANBUL) $(_MOCHA)
- $(ISTANBUL) cover $(_MOCHA) -- -u exports $(PRJ_DIR)test
+ $(ISTANBUL) cover $(_MOCHA) -- -u bdd $(PRJ_DIR)test/nodejs $(PRJ_DIR)test
$(JSHINT) $(MOCHA) $(_MOCHA) $(ISTANBUL) $(JSCS) $(BORSCHIK) $(KARMA):
npm install
diff --git a/dist/susanin.js b/dist/susanin.js
index 83fbb6f..5f00b34 100644
--- a/dist/susanin.js
+++ b/dist/susanin.js
@@ -219,8 +219,6 @@ function Route(options) {
options.conditions && typeof options.conditions === 'object' || (options.conditions = {});
options.defaults && typeof options.defaults === 'object' || (options.defaults = {});
- options.data && typeof options.data === 'object' || (options.data = {});
- typeof options.name === 'string' && (options.data.name = options.name);
if (options.isTrailingSlashOptional !== false) {
options.pattern += GROUP_OPENED_CHAR + PARAM_OPENED_CHAR +
@@ -340,7 +338,7 @@ Route.prototype._parseParams = function(pattern, parts) {
* @private
*/
Route.prototype._buildParseRegExp = function() {
- this._paramsMap = [];
+ this._reqExpParamsMap = [];
this._parseRegExpSource = '^' + this._buildParseRegExpParts(this._parts) + '$';
this._parseRegExp = new RegExp(this._parseRegExpSource);
};
@@ -360,10 +358,10 @@ Route.prototype._buildParseRegExpParts = function(parts) {
if (typeof part === 'string') {
ret += escape(part);
- } else if (part && part.what === 'param') {
- this._paramsMap.push(part.name);
+ } else if (part.what === 'param') {
+ this._reqExpParamsMap.push(part.name);
ret += '(' + this._buildParamValueRegExpSource(part.name) + ')';
- } else if (part && part.what === 'optional') {
+ } else {
ret += '(?:' + this._buildParseRegExpParts(part.parts) + ')?';
}
}
@@ -397,6 +395,7 @@ Route.prototype._buildParamValueRegExpSource = function(paramName) {
* @private
*/
Route.prototype._buildBuildFn = function() {
+ this._mainParamsMap = {};
this._buildFnSource = 'var h=({}).hasOwnProperty;return ' + this._buildBuildFnParts(this._parts) + ';';
/*jshint evil:true */
this._buildFn = new Function('p', this._buildFnSource);
@@ -418,14 +417,15 @@ Route.prototype._buildBuildFnParts = function(parts) {
if (typeof part === 'string') {
ret += '+"' + escape(part) + '"' ;
- } else if (part && part.what === 'param') {
+ } else if (part.what === 'param') {
+ this._mainParamsMap[part.name] = true;
ret += '+(h.call(p,"' + escape(part.name) + '")?' +
'p["' + escape(part.name) + '"]:' +
(has(defaults, part.name) ?
'"' + escape(defaults[part.name]) + '"' :
'""') +
')';
- } else if (part && part.what === 'optional') {
+ } else {
ret += '+((false';
for (j = 0, sizeJ = part.dependOnParams.length; j < sizeJ; ++j) {
@@ -447,11 +447,36 @@ Route.prototype._buildBuildFnParts = function(parts) {
};
/**
- * Matches object with route
- * @param {Object|String} matchObject
+ * @param {Object|Function} data
+ * @returns {Boolean}
+ * @private
+ */
+Route.prototype._isDataMatched = function(data) {
+ var routeData = this._options.data,
+ key;
+
+ if (typeof data === 'function') {
+ return Boolean(data(routeData));
+ } else if (data && typeof data === 'object') {
+ for (key in data) {
+ if (has(data, key)) {
+ if ( ! routeData || typeof routeData !== 'object' || routeData[key] !== data[key]) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Matches path with route
+ * @param {String} path
+ * @param {Function|Object} [data]
* @returns {Object|null}
*/
-Route.prototype.match = function(matchObject) {
+Route.prototype.match = function(path, data) {
var ret = null,
paramName,
matches,
@@ -462,55 +487,39 @@ Route.prototype.match = function(matchObject) {
filter = options.postMatch,
defaults = options.defaults;
- if (typeof matchObject === 'string') {
- matchObject = { path : matchObject };
- } else if ( ! matchObject) {
+ if (typeof path !== 'string' || (data && ! this._isDataMatched(data))) {
return ret;
}
- for (key in matchObject) {
- if (has(matchObject, key) && key !== 'path') {
- if (options.data[key] !== matchObject[key]) {
- return ret;
- }
- }
- }
+ matches = path.match(this._parseRegExp);
- if (typeof matchObject.path === 'string') {
- matches = matchObject.path.match(this._parseRegExp);
-
- if (matches) {
- ret = {};
-
- for (i = 1, size = matches.length; i < size; ++i) {
- if (typeof matches[i] !== 'undefined' && /* for IE lt 9*/ matches[i] !== '') {
- paramName = this._paramsMap[i - 1];
- if (paramName !== TRAILING_SLASH_PARAM_NAME) {
- ret[paramName] = matches[i];
- } else if (
- matchObject.path.charAt(matchObject.path.length - 2) === TRAILING_SLASH_PARAM_VALUE
- ) {
- return null;
- }
+ if (matches) {
+ ret = {};
+
+ for (i = 1, size = matches.length; i < size; ++i) {
+ if (typeof matches[i] !== 'undefined' && /* for IE lt 9*/ matches[i] !== '') {
+ paramName = this._reqExpParamsMap[i - 1];
+ if (paramName !== TRAILING_SLASH_PARAM_NAME) {
+ ret[paramName] = matches[i];
+ } else if (path.charAt(path.length - 2) === TRAILING_SLASH_PARAM_VALUE) {
+ return null;
}
}
+ }
- queryParams = querystring.parse(ret[QUERY_STRING_PARAM_NAME]);
- for (key in queryParams) {
- if (has(queryParams, key) && ! has(ret, key)) {
- ret[key] = queryParams[key];
- }
+ queryParams = querystring.parse(ret[QUERY_STRING_PARAM_NAME]);
+ for (key in queryParams) {
+ if (has(queryParams, key) && ! has(ret, key)) {
+ ret[key] = queryParams[key];
}
- delete ret[QUERY_STRING_PARAM_NAME];
+ }
+ delete ret[QUERY_STRING_PARAM_NAME];
- for (key in defaults) {
- if (has(defaults, key) && ! has(ret, key)) {
- ret[key] = defaults[key];
- }
+ for (key in defaults) {
+ if (has(defaults, key) && ! has(ret, key)) {
+ ret[key] = defaults[key];
}
}
- } else {
- ret = {};
}
if (ret && typeof filter === 'function') {
@@ -533,8 +542,6 @@ Route.prototype.build = function(params) {
queryParams = {},
queryString,
key,
- isMainParam,
- i, size,
filter = this._options.preBuild;
if (typeof filter === 'function') {
@@ -547,15 +554,7 @@ Route.prototype.build = function(params) {
params[key] !== null &&
typeof params[key] !== 'undefined'
) {
- isMainParam = false;
- for (i = 0, size = this._paramsMap.length; i < size; ++i) {
- if (this._paramsMap[i] === key) {
- isMainParam = true;
- break;
- }
- }
-
- if (isMainParam) {
+ if (this._mainParamsMap[key]) {
newParams[key] = params[key];
} else {
queryParams[key] = params[key];
@@ -579,10 +578,10 @@ Route.prototype.getData = function() {
/**
* Returns name of the route
- * @returns {?String}
+ * @returns {*}
*/
Route.prototype.getName = function() {
- return this._options.data.name;
+ return this._options.name;
};
module.exports = Route;
@@ -613,7 +612,7 @@ function Router() {
/**
* Add route
- * @param {Object|String} options
+ * @param {RouteOptions} options
* @returns {Route}
*/
Router.prototype.addRoute = function(options) {
@@ -631,17 +630,16 @@ Router.prototype.addRoute = function(options) {
/**
* Returns all successfully matched routes
- * @param {Object|String} matchObject
- * @returns {Array|null}
+ * @returns {[ Route, Object ][]}
*/
-Router.prototype.find = function(matchObject) {
+Router.prototype.find = function() {
var ret = [],
parsed,
i, size,
routes = this._routes;
for (i = 0, size = routes.length; i < size; ++i) {
- parsed = routes[i].match(matchObject);
+ parsed = routes[i].match.apply(routes[i], arguments);
if (parsed !== null) {
ret.push([ routes[i], parsed ]);
}
@@ -652,16 +650,15 @@ Router.prototype.find = function(matchObject) {
/**
* Returns first successfully matched route
- * @param {Object|String} matchObject
- * @returns {Array|null}
+ * @returns {[ Route, Object ]|null}
*/
-Router.prototype.findFirst = function(matchObject) {
+Router.prototype.findFirst = function() {
var parsed,
i, size,
routes = this._routes;
for (i = 0, size = routes.length; i < size; ++i) {
- parsed = routes[i].match(matchObject);
+ parsed = routes[i].match.apply(routes[i], arguments);
if (parsed !== null) {
return [ routes[i], parsed ];
}
diff --git a/dist/susanin.min.js b/dist/susanin.min.js
index e8df183..18aa688 100644
--- a/dist/susanin.min.js
+++ b/dist/susanin.min.js
@@ -1 +1 @@
-!function(t){function e(t){return{"./querystring":function(){var t={},e=Object.prototype.hasOwnProperty,r=Object.prototype.toString,n=function(t){return"[object Array]"===r.call(t)},o={decode:function(t){var e;try{e=decodeURIComponent(t.replace(/\+/g,"%20"))}catch(r){e=t}return e},parse:function(t,r,i){var a,s,p,u,f,h,l={};if("string"!=typeof t||""===t)return l;for(r||(r="&"),i||(i="="),a=t.split(r),f=0,h=a.length;h>f;++f)s=a[f].split(i),p="undefined"!=typeof s[1]?o.decode(s[1]):"",u=o.decode(s[0]),e.call(l,u)?n(l[u])?l[u].push(p):l[u]=[l[u],p]:l[u]=p;return l},stringify:function(t,r,n){var o,i,a,s,p,u,f="";if(!t)return f;r||(r="&"),n||(n="=");for(u in t)if(e.call(t,u))for(a=[].concat(t[u]),s=0,p=a.length;p>s;++s)i=typeof a[s],o="object"===i||"undefined"===i?"":encodeURIComponent(a[s]),f+=r+encodeURIComponent(u)+n+o;return f.substr(r.length)}};return t.exports=o,t.exports},"./route":function(){function t(e){if(!(this instanceof t))return new t(e);if("string"==typeof e&&(e={pattern:e}),!e||"object"!=typeof e)throw new Error("You must specify options");if("string"!=typeof e.pattern)throw new Error("You must specify the pattern of the route");this._options=e,e.conditions&&"object"==typeof e.conditions||(e.conditions={}),e.defaults&&"object"==typeof e.defaults||(e.defaults={}),e.data&&"object"==typeof e.data||(e.data={}),"string"==typeof e.name&&(e.data.name=e.name),e.isTrailingSlashOptional!==!1&&(e.pattern+=l+f+g+h+c,e.conditions[g]=b),e.pattern+=l+"?"+f+P+h+c,e.conditions[P]=".*",this._parts=this._parsePattern(e.pattern),this._buildParseRegExp(),this._buildBuildFn()}var r={},n=Object.prototype.hasOwnProperty,o=function(t,e){return n.call(t,e)},i=Object.prototype.toString,a=function(t){return"[object Array]"===i.call(t)},s=e("./querystring"),p=function(){var t=["/",".","*","+","?","|","(",")","[","]","{","}","\\"],e=new RegExp("(\\"+t.join("|\\")+")","g");return function(t){return t.replace(e,"\\$1")}}(),u=String(Math.random()).substr(2,5),f="<",h=">",l="(",c=")",d="[a-zA-Z_][\\w\\-]*",y="[\\w\\-\\.~]+",m=new RegExp("("+p(f)+d+p(h)+"|"+"[^"+p(f)+p(h)+"]+"+"|"+p(f)+"|"+p(h)+")","g"),g="ts_"+u,_="/",b=p("/"),P="qs_"+u;return t.prototype._parsePattern=function(t){for(var e,r,n,o=[],i="",a=0,s=0,p=!1,u=t.length;u>a;)if(e=t.charAt(a++),e===l)p?(++s,i+=e):(this._parseParams(i,o),i="",s=0,p=!0);else if(e===c)if(p)if(0===s){for(i={what:"optional",dependOnParams:[],parts:this._parsePattern(i)},o.push(i),r=0,n=i.parts.length;n>r;++r)i.parts[r]&&"param"===i.parts[r].what&&i.dependOnParams.push(i.parts[r].name);i="",p=!1}else--s,i+=e;else i+=e;else i+=e;return this._parseParams(i,o),o},t.prototype._parseParams=function(t,e){var r,n,o,i=t.match(m);if(i)for(r=0,n=i.length;n>r;++r)o=i[r],o.charAt(0)===f&&o.charAt(o.length-1)===h?e.push({what:"param",name:o.substr(1,o.length-2)}):e.push(o)},t.prototype._buildParseRegExp=function(){this._paramsMap=[],this._parseRegExpSource="^"+this._buildParseRegExpParts(this._parts)+"$",this._parseRegExp=new RegExp(this._parseRegExpSource)},t.prototype._buildParseRegExpParts=function(t){var e,r,n,o="";for(e=0,r=t.length;r>e;++e)n=t[e],"string"==typeof n?o+=p(n):n&&"param"===n.what?(this._paramsMap.push(n.name),o+="("+this._buildParamValueRegExpSource(n.name)+")"):n&&"optional"===n.what&&(o+="(?:"+this._buildParseRegExpParts(n.parts)+")?");return o},t.prototype._buildParamValueRegExpSource=function(t){var e,r=this._options.conditions[t];return e=r?a(r)?"(?:"+r.join("|")+")":r+"":y},t.prototype._buildBuildFn=function(){this._buildFnSource="var h=({}).hasOwnProperty;return "+this._buildBuildFnParts(this._parts)+";",this._buildFn=new Function("p",this._buildFnSource)},t.prototype._buildBuildFnParts=function(t){var e,r,n,i,a,s,u='""',f=this._options.defaults;for(e=0,r=t.length;r>e;++e)if(a=t[e],"string"==typeof a)u+='+"'+p(a)+'"';else if(a&&"param"===a.what)u+='+(h.call(p,"'+p(a.name)+'")?'+'p["'+p(a.name)+'"]:'+(o(f,a.name)?'"'+p(f[a.name])+'"':'""')+")";else if(a&&"optional"===a.what){for(u+="+((false",n=0,i=a.dependOnParams.length;i>n;++n)s=a.dependOnParams[n],u+='||(h.call(p,"'+p(s)+'")'+(o(f,s)?'&&p["'+p(s)+'"]!=="'+p(f[s])+'"':"")+")";u+=")?("+this._buildBuildFnParts(a.parts)+'):"")'}return u},t.prototype.match=function(t){var e,r,n,i,a,p,u=null,f=this._options,h=f.postMatch,l=f.defaults;if("string"==typeof t)t={path:t};else if(!t)return u;for(a in t)if(o(t,a)&&"path"!==a&&f.data[a]!==t[a])return u;if("string"==typeof t.path){if(r=t.path.match(this._parseRegExp)){for(u={},n=1,i=r.length;i>n;++n)if("undefined"!=typeof r[n]&&""!==r[n])if(e=this._paramsMap[n-1],e!==g)u[e]=r[n];else if(t.path.charAt(t.path.length-2)===_)return null;p=s.parse(u[P]);for(a in p)o(p,a)&&!o(u,a)&&(u[a]=p[a]);delete u[P];for(a in l)o(l,a)&&!o(u,a)&&(u[a]=l[a])}}else u={};return u&&"function"==typeof h&&(u=h(u),u&&"object"==typeof u||(u=null)),u},t.prototype.build=function(t){var e,r,n,i,a,p={},u={},f=this._options.preBuild;"function"==typeof f&&(t=f(t));for(r in t)if(o(t,r)&&null!==t[r]&&"undefined"!=typeof t[r]){for(n=!1,i=0,a=this._paramsMap.length;a>i;++i)if(this._paramsMap[i]===r){n=!0;break}n?p[r]=t[r]:u[r]=t[r]}return e=s.stringify(u),e&&(p[P]=e),this._buildFn(p)},t.prototype.getData=function(){return this._options.data},t.prototype.getName=function(){return this._options.data.name},r.exports=t,r.exports},"./router":function(){function t(){return this instanceof t?(this._routes=[],this._routesByName={},void 0):new t}var r={},n=e("./route");return t.prototype.addRoute=function(t){var e,r;return e=new n(t),this._routes.push(e),r=e.getName(),r&&(this._routesByName[r]=e),e},t.prototype.find=function(t){var e,r,n,o=[],i=this._routes;for(r=0,n=i.length;n>r;++r)e=i[r].match(t),null!==e&&o.push([i[r],e]);return o},t.prototype.findFirst=function(t){var e,r,n,o=this._routes;for(r=0,n=o.length;n>r;++r)if(e=o[r].match(t),null!==e)return[o[r],e];return null},t.prototype.getRouteByName=function(t){return this._routesByName[t]||null},t.Route=n,r.exports=t,r.exports}}[t]()}var r=e("./router"),n=!0;t.module&&"object"==typeof module.exports&&(module.exports=r,n=!1),t.modules&&modules.define&&modules.require&&(modules.define("susanin",function(t){t(r)}),n=!1),"function"==typeof t.define&&define.amd&&(define(function(){return r}),n=!1),n&&(t.Susanin=r)}(this);
\ No newline at end of file
+!function(t){function e(t){return{"./querystring":function(){var t={},e=Object.prototype.hasOwnProperty,r=Object.prototype.toString,n=function(t){return"[object Array]"===r.call(t)},o={decode:function(t){var e;try{e=decodeURIComponent(t.replace(/\+/g,"%20"))}catch(r){e=t}return e},parse:function(t,r,i){var a,s,p,u,f,l,h={};if("string"!=typeof t||""===t)return h;for(r||(r="&"),i||(i="="),a=t.split(r),f=0,l=a.length;l>f;++f)s=a[f].split(i),p="undefined"!=typeof s[1]?o.decode(s[1]):"",u=o.decode(s[0]),e.call(h,u)?n(h[u])?h[u].push(p):h[u]=[h[u],p]:h[u]=p;return h},stringify:function(t,r,n){var o,i,a,s,p,u,f="";if(!t)return f;r||(r="&"),n||(n="=");for(u in t)if(e.call(t,u))for(a=[].concat(t[u]),s=0,p=a.length;p>s;++s)i=typeof a[s],o="object"===i||"undefined"===i?"":encodeURIComponent(a[s]),f+=r+encodeURIComponent(u)+n+o;return f.substr(r.length)}};return t.exports=o,t.exports},"./route":function(){function t(e){if(!(this instanceof t))return new t(e);if("string"==typeof e&&(e={pattern:e}),!e||"object"!=typeof e)throw new Error("You must specify options");if("string"!=typeof e.pattern)throw new Error("You must specify the pattern of the route");this._options=e,e.conditions&&"object"==typeof e.conditions||(e.conditions={}),e.defaults&&"object"==typeof e.defaults||(e.defaults={}),e.isTrailingSlashOptional!==!1&&(e.pattern+=h+f+_+l+c,e.conditions[_]=b),e.pattern+=h+"?"+f+P+l+c,e.conditions[P]=".*",this._parts=this._parsePattern(e.pattern),this._buildParseRegExp(),this._buildBuildFn()}var r={},n=Object.prototype.hasOwnProperty,o=function(t,e){return n.call(t,e)},i=Object.prototype.toString,a=function(t){return"[object Array]"===i.call(t)},s=e("./querystring"),p=function(){var t=["/",".","*","+","?","|","(",")","[","]","{","}","\\"],e=new RegExp("(\\"+t.join("|\\")+")","g");return function(t){return t.replace(e,"\\$1")}}(),u=String(Math.random()).substr(2,5),f="<",l=">",h="(",c=")",d="[a-zA-Z_][\\w\\-]*",m="[\\w\\-\\.~]+",y=new RegExp("("+p(f)+d+p(l)+"|"+"[^"+p(f)+p(l)+"]+"+"|"+p(f)+"|"+p(l)+")","g"),_="ts_"+u,g="/",b=p("/"),P="qs_"+u;return t.prototype._parsePattern=function(t){for(var e,r,n,o=[],i="",a=0,s=0,p=!1,u=t.length;u>a;)if(e=t.charAt(a++),e===h)p?(++s,i+=e):(this._parseParams(i,o),i="",s=0,p=!0);else if(e===c)if(p)if(0===s){for(i={what:"optional",dependOnParams:[],parts:this._parsePattern(i)},o.push(i),r=0,n=i.parts.length;n>r;++r)i.parts[r]&&"param"===i.parts[r].what&&i.dependOnParams.push(i.parts[r].name);i="",p=!1}else--s,i+=e;else i+=e;else i+=e;return this._parseParams(i,o),o},t.prototype._parseParams=function(t,e){var r,n,o,i=t.match(y);if(i)for(r=0,n=i.length;n>r;++r)o=i[r],o.charAt(0)===f&&o.charAt(o.length-1)===l?e.push({what:"param",name:o.substr(1,o.length-2)}):e.push(o)},t.prototype._buildParseRegExp=function(){this._reqExpParamsMap=[],this._parseRegExpSource="^"+this._buildParseRegExpParts(this._parts)+"$",this._parseRegExp=new RegExp(this._parseRegExpSource)},t.prototype._buildParseRegExpParts=function(t){var e,r,n,o="";for(e=0,r=t.length;r>e;++e)n=t[e],"string"==typeof n?o+=p(n):"param"===n.what?(this._reqExpParamsMap.push(n.name),o+="("+this._buildParamValueRegExpSource(n.name)+")"):o+="(?:"+this._buildParseRegExpParts(n.parts)+")?";return o},t.prototype._buildParamValueRegExpSource=function(t){var e,r=this._options.conditions[t];return e=r?a(r)?"(?:"+r.join("|")+")":r+"":m},t.prototype._buildBuildFn=function(){this._mainParamsMap={},this._buildFnSource="var h=({}).hasOwnProperty;return "+this._buildBuildFnParts(this._parts)+";",this._buildFn=new Function("p",this._buildFnSource)},t.prototype._buildBuildFnParts=function(t){var e,r,n,i,a,s,u='""',f=this._options.defaults;for(e=0,r=t.length;r>e;++e)if(a=t[e],"string"==typeof a)u+='+"'+p(a)+'"';else if("param"===a.what)this._mainParamsMap[a.name]=!0,u+='+(h.call(p,"'+p(a.name)+'")?'+'p["'+p(a.name)+'"]:'+(o(f,a.name)?'"'+p(f[a.name])+'"':'""')+")";else{for(u+="+((false",n=0,i=a.dependOnParams.length;i>n;++n)s=a.dependOnParams[n],u+='||(h.call(p,"'+p(s)+'")'+(o(f,s)?'&&p["'+p(s)+'"]!=="'+p(f[s])+'"':"")+")";u+=")?("+this._buildBuildFnParts(a.parts)+'):"")'}return u},t.prototype._isDataMatched=function(t){var e,r=this._options.data;if("function"==typeof t)return Boolean(t(r));if(t&&"object"==typeof t)for(e in t)if(o(t,e)&&(!r||"object"!=typeof r||r[e]!==t[e]))return!1;return!0},t.prototype.match=function(t,e){var r,n,i,a,p,u,f=null,l=this._options,h=l.postMatch,c=l.defaults;if("string"!=typeof t||e&&!this._isDataMatched(e))return f;if(n=t.match(this._parseRegExp)){for(f={},i=1,a=n.length;a>i;++i)if("undefined"!=typeof n[i]&&""!==n[i])if(r=this._reqExpParamsMap[i-1],r!==_)f[r]=n[i];else if(t.charAt(t.length-2)===g)return null;u=s.parse(f[P]);for(p in u)o(u,p)&&!o(f,p)&&(f[p]=u[p]);delete f[P];for(p in c)o(c,p)&&!o(f,p)&&(f[p]=c[p])}return f&&"function"==typeof h&&(f=h(f),f&&"object"==typeof f||(f=null)),f},t.prototype.build=function(t){var e,r,n={},i={},a=this._options.preBuild;"function"==typeof a&&(t=a(t));for(r in t)o(t,r)&&null!==t[r]&&"undefined"!=typeof t[r]&&(this._mainParamsMap[r]?n[r]=t[r]:i[r]=t[r]);return e=s.stringify(i),e&&(n[P]=e),this._buildFn(n)},t.prototype.getData=function(){return this._options.data},t.prototype.getName=function(){return this._options.name},r.exports=t,r.exports},"./router":function(){function t(){return this instanceof t?(this._routes=[],this._routesByName={},void 0):new t}var r={},n=e("./route");return t.prototype.addRoute=function(t){var e,r;return e=new n(t),this._routes.push(e),r=e.getName(),r&&(this._routesByName[r]=e),e},t.prototype.find=function(){var t,e,r,n=[],o=this._routes;for(e=0,r=o.length;r>e;++e)t=o[e].match.apply(o[e],arguments),null!==t&&n.push([o[e],t]);return n},t.prototype.findFirst=function(){var t,e,r,n=this._routes;for(e=0,r=n.length;r>e;++e)if(t=n[e].match.apply(n[e],arguments),null!==t)return[n[e],t];return null},t.prototype.getRouteByName=function(t){return this._routesByName[t]||null},t.Route=n,r.exports=t,r.exports}}[t]()}var r=e("./router"),n=!0;t.module&&"object"==typeof module.exports&&(module.exports=r,n=!1),t.modules&&modules.define&&modules.require&&(modules.define("susanin",function(t){t(r)}),n=!1),"function"==typeof t.define&&define.amd&&(define(function(){return r}),n=!1),n&&(t.Susanin=r)}(this);
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
index 8450427..9d9b102 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -15,7 +15,7 @@ module.exports = function(config) {
files : [
'dist/susanin.min.js',
'node_modules/chai/chai.js',
- 'test/lib/browser.js',
+ 'test/browser/*.js',
'lib/querystring.js',
'test/*.js'
],
diff --git a/lib/route.js b/lib/route.js
index aeffb8c..0e9c25d 100644
--- a/lib/route.js
+++ b/lib/route.js
@@ -85,8 +85,6 @@ function Route(options) {
options.conditions && typeof options.conditions === 'object' || (options.conditions = {});
options.defaults && typeof options.defaults === 'object' || (options.defaults = {});
- options.data && typeof options.data === 'object' || (options.data = {});
- typeof options.name === 'string' && (options.data.name = options.name);
if (options.isTrailingSlashOptional !== false) {
options.pattern += GROUP_OPENED_CHAR + PARAM_OPENED_CHAR +
@@ -206,7 +204,7 @@ Route.prototype._parseParams = function(pattern, parts) {
* @private
*/
Route.prototype._buildParseRegExp = function() {
- this._paramsMap = [];
+ this._reqExpParamsMap = [];
this._parseRegExpSource = '^' + this._buildParseRegExpParts(this._parts) + '$';
this._parseRegExp = new RegExp(this._parseRegExpSource);
};
@@ -226,10 +224,10 @@ Route.prototype._buildParseRegExpParts = function(parts) {
if (typeof part === 'string') {
ret += escape(part);
- } else if (part && part.what === 'param') {
- this._paramsMap.push(part.name);
+ } else if (part.what === 'param') {
+ this._reqExpParamsMap.push(part.name);
ret += '(' + this._buildParamValueRegExpSource(part.name) + ')';
- } else if (part && part.what === 'optional') {
+ } else {
ret += '(?:' + this._buildParseRegExpParts(part.parts) + ')?';
}
}
@@ -263,6 +261,7 @@ Route.prototype._buildParamValueRegExpSource = function(paramName) {
* @private
*/
Route.prototype._buildBuildFn = function() {
+ this._mainParamsMap = {};
this._buildFnSource = 'var h=({}).hasOwnProperty;return ' + this._buildBuildFnParts(this._parts) + ';';
/*jshint evil:true */
this._buildFn = new Function('p', this._buildFnSource);
@@ -284,14 +283,15 @@ Route.prototype._buildBuildFnParts = function(parts) {
if (typeof part === 'string') {
ret += '+"' + escape(part) + '"' ;
- } else if (part && part.what === 'param') {
+ } else if (part.what === 'param') {
+ this._mainParamsMap[part.name] = true;
ret += '+(h.call(p,"' + escape(part.name) + '")?' +
'p["' + escape(part.name) + '"]:' +
(has(defaults, part.name) ?
'"' + escape(defaults[part.name]) + '"' :
'""') +
')';
- } else if (part && part.what === 'optional') {
+ } else {
ret += '+((false';
for (j = 0, sizeJ = part.dependOnParams.length; j < sizeJ; ++j) {
@@ -313,11 +313,36 @@ Route.prototype._buildBuildFnParts = function(parts) {
};
/**
- * Matches object with route
- * @param {Object|String} matchObject
+ * @param {Object|Function} data
+ * @returns {Boolean}
+ * @private
+ */
+Route.prototype._isDataMatched = function(data) {
+ var routeData = this._options.data,
+ key;
+
+ if (typeof data === 'function') {
+ return Boolean(data(routeData));
+ } else if (data && typeof data === 'object') {
+ for (key in data) {
+ if (has(data, key)) {
+ if ( ! routeData || typeof routeData !== 'object' || routeData[key] !== data[key]) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Matches path with route
+ * @param {String} path
+ * @param {Function|Object} [data]
* @returns {Object|null}
*/
-Route.prototype.match = function(matchObject) {
+Route.prototype.match = function(path, data) {
var ret = null,
paramName,
matches,
@@ -328,55 +353,39 @@ Route.prototype.match = function(matchObject) {
filter = options.postMatch,
defaults = options.defaults;
- if (typeof matchObject === 'string') {
- matchObject = { path : matchObject };
- } else if ( ! matchObject) {
+ if (typeof path !== 'string' || (data && ! this._isDataMatched(data))) {
return ret;
}
- for (key in matchObject) {
- if (has(matchObject, key) && key !== 'path') {
- if (options.data[key] !== matchObject[key]) {
- return ret;
- }
- }
- }
+ matches = path.match(this._parseRegExp);
- if (typeof matchObject.path === 'string') {
- matches = matchObject.path.match(this._parseRegExp);
-
- if (matches) {
- ret = {};
-
- for (i = 1, size = matches.length; i < size; ++i) {
- if (typeof matches[i] !== 'undefined' && /* for IE lt 9*/ matches[i] !== '') {
- paramName = this._paramsMap[i - 1];
- if (paramName !== TRAILING_SLASH_PARAM_NAME) {
- ret[paramName] = matches[i];
- } else if (
- matchObject.path.charAt(matchObject.path.length - 2) === TRAILING_SLASH_PARAM_VALUE
- ) {
- return null;
- }
+ if (matches) {
+ ret = {};
+
+ for (i = 1, size = matches.length; i < size; ++i) {
+ if (typeof matches[i] !== 'undefined' && /* for IE lt 9*/ matches[i] !== '') {
+ paramName = this._reqExpParamsMap[i - 1];
+ if (paramName !== TRAILING_SLASH_PARAM_NAME) {
+ ret[paramName] = matches[i];
+ } else if (path.charAt(path.length - 2) === TRAILING_SLASH_PARAM_VALUE) {
+ return null;
}
}
+ }
- queryParams = querystring.parse(ret[QUERY_STRING_PARAM_NAME]);
- for (key in queryParams) {
- if (has(queryParams, key) && ! has(ret, key)) {
- ret[key] = queryParams[key];
- }
+ queryParams = querystring.parse(ret[QUERY_STRING_PARAM_NAME]);
+ for (key in queryParams) {
+ if (has(queryParams, key) && ! has(ret, key)) {
+ ret[key] = queryParams[key];
}
- delete ret[QUERY_STRING_PARAM_NAME];
+ }
+ delete ret[QUERY_STRING_PARAM_NAME];
- for (key in defaults) {
- if (has(defaults, key) && ! has(ret, key)) {
- ret[key] = defaults[key];
- }
+ for (key in defaults) {
+ if (has(defaults, key) && ! has(ret, key)) {
+ ret[key] = defaults[key];
}
}
- } else {
- ret = {};
}
if (ret && typeof filter === 'function') {
@@ -399,8 +408,6 @@ Route.prototype.build = function(params) {
queryParams = {},
queryString,
key,
- isMainParam,
- i, size,
filter = this._options.preBuild;
if (typeof filter === 'function') {
@@ -413,15 +420,7 @@ Route.prototype.build = function(params) {
params[key] !== null &&
typeof params[key] !== 'undefined'
) {
- isMainParam = false;
- for (i = 0, size = this._paramsMap.length; i < size; ++i) {
- if (this._paramsMap[i] === key) {
- isMainParam = true;
- break;
- }
- }
-
- if (isMainParam) {
+ if (this._mainParamsMap[key]) {
newParams[key] = params[key];
} else {
queryParams[key] = params[key];
@@ -445,10 +444,10 @@ Route.prototype.getData = function() {
/**
* Returns name of the route
- * @returns {?String}
+ * @returns {*}
*/
Route.prototype.getName = function() {
- return this._options.data.name;
+ return this._options.name;
};
module.exports = Route;
diff --git a/lib/router.js b/lib/router.js
index 29b1e64..b1fe8a0 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -15,7 +15,7 @@ function Router() {
/**
* Add route
- * @param {Object|String} options
+ * @param {RouteOptions} options
* @returns {Route}
*/
Router.prototype.addRoute = function(options) {
@@ -33,17 +33,16 @@ Router.prototype.addRoute = function(options) {
/**
* Returns all successfully matched routes
- * @param {Object|String} matchObject
- * @returns {Array|null}
+ * @returns {[ Route, Object ][]}
*/
-Router.prototype.find = function(matchObject) {
+Router.prototype.find = function() {
var ret = [],
parsed,
i, size,
routes = this._routes;
for (i = 0, size = routes.length; i < size; ++i) {
- parsed = routes[i].match(matchObject);
+ parsed = routes[i].match.apply(routes[i], arguments);
if (parsed !== null) {
ret.push([ routes[i], parsed ]);
}
@@ -54,16 +53,15 @@ Router.prototype.find = function(matchObject) {
/**
* Returns first successfully matched route
- * @param {Object|String} matchObject
- * @returns {Array|null}
+ * @returns {[ Route, Object ]|null}
*/
-Router.prototype.findFirst = function(matchObject) {
+Router.prototype.findFirst = function() {
var parsed,
i, size,
routes = this._routes;
for (i = 0, size = routes.length; i < size; ++i) {
- parsed = routes[i].match(matchObject);
+ parsed = routes[i].match.apply(routes[i], arguments);
if (parsed !== null) {
return [ routes[i], parsed ];
}
diff --git a/test/lib/browser.js b/test/browser/setup.js
similarity index 100%
rename from test/lib/browser.js
rename to test/browser/setup.js
diff --git a/test/data_matching.js b/test/data_matching.js
new file mode 100644
index 0000000..bf7cfed
--- /dev/null
+++ b/test/data_matching.js
@@ -0,0 +1,73 @@
+/* global describe, it, Router, assert */
+
+describe('route.match() with data', function() {
+ var Route = Router.Route;
+
+ it('routeData is absent', function(done) {
+ var route = Route('/opa');
+
+ assert.deepEqual(route.match('/opapa'), null);
+ assert.deepEqual(route.match('/opa'), {});
+ assert.deepEqual(route.match('/opa', {}), {});
+ assert.deepEqual(route.match('/opa', { method : 'get' }), null);
+
+ done();
+ });
+
+ it('data is an object', function(done) {
+ function Data() {}
+ Data.prototype = { method : 'post' };
+
+ var routeData = {
+ method : 'get',
+ foo : [ 'bar1', 'bar2' ]
+ },
+ route = Route({
+ pattern : '/opa',
+ data : routeData
+ });
+
+ assert.deepEqual(route.match('/opapa'), null);
+ assert.deepEqual(route.match('/opa'), {});
+ assert.deepEqual(route.match('/opa', null), {});
+ assert.deepEqual(route.match('/opa', {}), {});
+ assert.deepEqual(route.match('/opa', 1), {});
+ assert.deepEqual(route.match('/opa', 'a'), {});
+ assert.deepEqual(route.match('/opa', { method : 'post' }), null);
+ assert.deepEqual(route.match('/opa', { method : 'get' }), {});
+ assert.deepEqual(route.match('/opa', { foo : 'bar1' }), null);
+ assert.deepEqual(route.match('/opa', { foo : 'bar2' }), null);
+ assert.deepEqual(route.match('/opa', { foo : [ 'bar1', 'bar2' ] }), null);
+ assert.deepEqual(route.match('/opa', { foo : routeData.foo }), {});
+ assert.deepEqual(route.match('/opa', { method : 'get', foo : routeData.foo }), {});
+ assert.deepEqual(route.match('/opa', { method : 'post', foo : routeData.foo }), null);
+ assert.deepEqual(route.match('/opa', new Data()), {});
+
+ done();
+ });
+
+ it('data is a function', function(done) {
+ var routeData = {
+ method : 'get',
+ foo : [ 'bar1', 'bar2' ]
+ },
+ route = Route({
+ pattern : '/opa',
+ data : routeData
+ }),
+ undef;
+
+ [ true, false, 0, 1, '', 'a', undef, {} ].forEach(function(value) {
+ assert.deepEqual(route.match('/opa', function() {
+ return value;
+ }), Boolean(value) ? {} : null);
+ });
+
+ route.match('/opa', function(data) {
+ assert.deepEqual(data, routeData);
+ });
+
+ done();
+ });
+
+});
diff --git a/test/lib/nodejs.js b/test/nodejs/setup.js
similarity index 66%
rename from test/lib/nodejs.js
rename to test/nodejs/setup.js
index 8d83173..04b96f7 100644
--- a/test/lib/nodejs.js
+++ b/test/nodejs/setup.js
@@ -1,3 +1,3 @@
-global.Router = require('../..');
+global.Router = require('../../lib/router.js');
global.assert = require('chai').assert;
global.querystring = require('../../lib/querystring');
diff --git a/test/querystring.js b/test/querystring.js
index 3d8cd27..6f0b545 100644
--- a/test/querystring.js
+++ b/test/querystring.js
@@ -30,6 +30,11 @@ describe('querystring module', function() {
});
it('route.stringify()', function(done) {
+ function Params() {
+ this.bla = 'foo';
+ }
+ Params.prototype = { bla1 : 'foo1' };
+
assert.strictEqual(qs.stringify(), '');
assert.strictEqual(qs.stringify(null), '');
assert.strictEqual(qs.stringify(undef), '');
@@ -48,6 +53,7 @@ describe('querystring module', function() {
assert.strictEqual(qs.stringify({ bla : [ 'foo1', 'foo2' ] }), 'bla=foo1&bla=foo2');
assert.strictEqual(qs.stringify({ bla : 'foo', bla1 : 'foo1' }), 'bla=foo&bla1=foo1');
assert.strictEqual(qs.stringify({ bla : 'foo', bla1 : 'foo1' }), 'bla=foo&bla1=foo1');
+ assert.strictEqual(qs.stringify(new Params()), 'bla=foo');
assert.strictEqual(qs.stringify([ 1, 2, 3 ]), '0=1&1=2&2=3');
done();
diff --git a/test/route.getData.js b/test/route.getData.js
index 46178ac..db8f9a7 100644
--- a/test/route.getData.js
+++ b/test/route.getData.js
@@ -4,8 +4,10 @@ describe('route.getData()', function() {
var Route = Router.Route;
it('route.getData() must return right data', function(done) {
- assert.deepEqual(Route('/opa').getData(), {});
- assert.deepEqual(Route({ pattern : '/opa' }).getData(), {});
+ var undef;
+
+ assert.deepEqual(Route('/opa').getData(), undef);
+ assert.deepEqual(Route({ pattern : '/opa' }).getData(), undef);
assert.deepEqual(Route({ pattern : '/opa', data : { foo : 'bar' } }).getData(), { foo : 'bar' });
done();
diff --git a/test/route.getName.js b/test/route.getName.js
index e586b8c..8cd0746 100644
--- a/test/route.getName.js
+++ b/test/route.getName.js
@@ -4,9 +4,10 @@ describe('route.getName()', function() {
var Route = Router.Route;
it('route.getName() must return right data', function(done) {
- assert.strictEqual(Route('/opa').getName(), undefined);
+ var undef;
+
+ assert.strictEqual(Route('/opa').getName(), undef);
assert.strictEqual(Route({ pattern : '/opa', name : 'opa' }).getName(), 'opa');
- assert.strictEqual(Route({ pattern : '/opa', data : { name : 'opa' } }).getName(), 'opa');
done();
});
diff --git a/test/route.match.js b/test/route.match.js
index 01fa126..8c2ec84 100644
--- a/test/route.match.js
+++ b/test/route.match.js
@@ -133,6 +133,23 @@ describe('route.match()', function() {
done();
});
+ it('/opa)(/opapa/)', function(done) {
+ var route = Route({
+ pattern : '/opa)(/opapa/)'
+ });
+
+ assert.deepEqual(route.match('/opa'), null);
+ assert.deepEqual(route.match('/opa)/'), {});
+ assert.deepEqual(route.match('/opa)'), {});
+ assert.deepEqual(route.match('/opa)/value'), null);
+ assert.deepEqual(route.match('/opa)/opapa/'), null);
+ assert.deepEqual(route.match('/opa)/opapa/value'), { param : 'value' });
+ assert.deepEqual(route.match('/opa)?foo1=bar1&foo1=bar2&foo2=&=bar3'),
+ { foo1 : [ 'bar1', 'bar2' ], foo2 : '', '' : 'bar3' });
+
+ done();
+ });
+
it('/opa(/opapa/) and defaults', function(done) {
var route = Route({
pattern : '/opa(/opapa/)',
diff --git a/test/router.addRoute.js b/test/router.addRoute.js
index bf36495..86bb36b 100644
--- a/test/router.addRoute.js
+++ b/test/router.addRoute.js
@@ -1,6 +1,6 @@
/* global describe, it, Router, assert */
-describe('route.match()', function() {
+describe('route.addRoute()', function() {
var Route = Router.Route;
it('Instance of `Router` must have function `addRoute`', function(done) {
diff --git a/test/router.find.js b/test/router.find.js
index 53aa38e..0facf46 100644
--- a/test/router.find.js
+++ b/test/router.find.js
@@ -41,7 +41,7 @@ describe('router.find*', function() {
assert.strictEqual(finded[0], this.router.getRouteByName('first'));
assert.deepEqual(finded[1], {});
- assert.strictEqual(this.router.findFirst({ path : '/first', method : 'post' })[0], this.router.getRouteByName('third'));
+ assert.strictEqual(this.router.findFirst('/first', { method : 'post' })[0], this.router.getRouteByName('third'));
assert.strictEqual(this.router.findFirst('/f'), null);
done();
@@ -57,7 +57,7 @@ describe('router.find*', function() {
assert.deepEqual(finded[0][1], {});
assert.deepEqual(finded[1][1], {});
assert.deepEqual(finded[2][1], {});
- assert.strictEqual(this.router.find({ path : '/first', method : 'post' })[0][0], this.router.getRouteByName('third'));
+ assert.strictEqual(this.router.find('/first', { method : 'post' })[0][0], this.router.getRouteByName('third'));
assert.deepEqual(this.router.find('/f'), []);
done();
diff --git a/test/router.getRouteByName.js b/test/router.getRouteByName.js
index 0bbfb53..22fcf51 100644
--- a/test/router.getRouteByName.js
+++ b/test/router.getRouteByName.js
@@ -4,8 +4,8 @@ describe('router.getRouteByName()', function() {
it('`getRouteByName` must return right instance of `Route`', function(done) {
var router = Router(),
- routeFoo = router.addRoute({ pattern : '/foo', data : { name : 'foo' } }),
- routeBar = router.addRoute({ pattern : '/bar', data : { name : 'bar' } });
+ routeFoo = router.addRoute({ pattern : '/foo', name : 'foo' }),
+ routeBar = router.addRoute({ pattern : '/bar', name : 'bar' });
assert.strictEqual(router.getRouteByName('bar'), routeBar);
assert.strictEqual(router.getRouteByName('foo'), routeFoo);