Skip to content

Commit

Permalink
Refactor patternLexer to include interpolate() and change rest params…
Browse files Browse the repository at this point in the history
… validation.

Reuse TOKENS for interpolate to avoid getting out of sync. see #34
  • Loading branch information
millermedeiros committed Apr 18, 2012
1 parent c721f86 commit 625ee7e
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 123 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,8 @@
### Other

- change default behavior of slashes at begin/end of request (#35)
- change the way rest segments are validated through `route.rules`, now it
should use the segment ID without the "*" (eg. `foo` instead of `foo*`).



Expand Down
96 changes: 63 additions & 33 deletions dev/src/pattern_lexer.js
Expand Up @@ -12,55 +12,47 @@
UNNECESSARY_SLASHES_REGEXP = /^\/|\/$/g,

//params - everything between `{ }` or `: :`
PARAMS_REGEXP = /(?:\{|:)([^}:]+)(?:\}|:)/g,

//optional params - everything between `: :`
OPTIONAL_PARAMS_REGEXP = /:([^:]+):/g,
PARAMS_REGEXP = /(?:\{|:)([^}:*]+)\*?(?:\}|:)/g,

//used to save params during compile (avoid escaping things that
//shouldn't be escaped).
TOKENS = [
{
TOKENS = {
'OS' : {
//optional slashes
//slash between `::` or `}:` or `\w:`. $1 = before, $2 = after
rgx : /([:}]|\w(?=\/))\/?(:)/g,
save : '$1{{id}}$2',
id : 'OS',
res : '\\/?'
},
{
'RS' : {
//required slashes
//slash between `::` or `}:` or `\w:`. $1 = before, $2 = after
rgx : /([:}])\/?(\{)/g,
save : '$1{{id}}$2',
id : 'RS',
res : '\\/'
},
{
'OR' : {
//optional rest - everything in between `: *:`
rgx : /:([^:]+)\*:/g,
id : 'OR',
res : '(.*)?' // optional group to avoid passing empty string as captured
},
{
'RR' : {
//rest param - everything in between `{ *}`
rgx : /\{([^}]+)\*\}/g,
id : 'RR',
res : '(.+)'
},
{
// required/optional params should come after rest segments
'RP' : {
//required params - everything between `{ }`
rgx : /\{([^}]+)\}/g,
id : 'RP',
rgx : /\{([^}*]+)\*?\}/g,
res : '([^\\/]+)'
},
{
//optional params
rgx : OPTIONAL_PARAMS_REGEXP,
id : 'OP',
'OP' : {
//optional params - everything between `: :`
rgx : /:([^:*]+)\*?:/g,
res : '([^\\/]+)?\/?'
}
],
},

LOOSE_SLASH = 1,
STRICT_SLASH = 2,
Expand All @@ -69,13 +61,14 @@


function precompileTokens(){
var n = TOKENS.length,
cur,
id;
while (cur = TOKENS[--n]) {
id = '__CR_'+ cur.id +'__';
cur.save = ('save' in cur)? cur.save.replace('{{id}}', id) : id;
cur.rRestore = new RegExp(id, 'g');
var key, cur;
for (key in TOKENS) {
if (TOKENS.hasOwnProperty(key)) {
cur = TOKENS[key];
cur.id = '__CR_'+ key +'__';
cur.save = ('save' in cur)? cur.save.replace('{{id}}', cur.id) : cur.id;
cur.rRestore = new RegExp(cur.id, 'g');
}
}
}
precompileTokens();
Expand All @@ -94,7 +87,7 @@
}

function getOptionalParamsIds(pattern) {
return captureVals(OPTIONAL_PARAMS_REGEXP, pattern);
return captureVals(TOKENS.OP.rgx, pattern);
}

function compilePattern(pattern) {
Expand All @@ -120,9 +113,12 @@
}

function replaceTokens(pattern, regexpName, replaceName) {
var i = 0, cur;
while (cur = TOKENS[i++]) {
pattern = pattern.replace(cur[regexpName], cur[replaceName]);
var cur, key;
for (key in TOKENS) {
if (TOKENS.hasOwnProperty(key)) {
cur = TOKENS[key];
pattern = pattern.replace(cur[regexpName], cur[replaceName]);
}
}
return pattern;
}
Expand All @@ -138,6 +134,39 @@
return vals;
}

function interpolate(pattern, replacements) {
if (typeof pattern !== 'string') {
throw new Error('Route pattern should be a string.');
}

var replaceFn = function(match, prop){
var val;
if (prop in replacements) {
val = replacements[prop];
if (match.indexOf('*') === -1 && val.indexOf('/') !== -1) {
throw new Error('Invalid value "'+ val +'" for segment "'+ match +'".');
}
}
else if (match.indexOf('{') !== -1) {
throw new Error('The segment '+ match +' is required.');
}
else {
val = '';
}
return val;
};

if (! TOKENS.OS.trail) {
TOKENS.OS.trail = new RegExp('(?:'+ TOKENS.OS.id +')+$');
}

return pattern
.replace(TOKENS.OS.rgx, TOKENS.OS.save)
.replace(PARAMS_REGEXP, replaceFn)
.replace(TOKENS.OS.trail, '') // remove trailing
.replace(TOKENS.OS.rRestore, '/'); // add slash between segments
}

//API
return {
strict : function(){
Expand All @@ -149,7 +178,8 @@
getParamIds : getParamIds,
getOptionalParamsIds : getOptionalParamsIds,
getParamValues : getParamValues,
compilePattern : compilePattern
compilePattern : compilePattern,
interpolate : interpolate
};

}());
Expand Down
27 changes: 1 addition & 26 deletions dev/src/route.js
Expand Up @@ -94,32 +94,7 @@
},

interpolate : function(replacements) {
if (typeof this._pattern !== 'string') {
throw new Error('Route pattern should be a string.');
}
var replaceFn = function(match, prop){
if (prop in replacements) {
return replacements[prop];
} else if (match.indexOf('{') !== -1){
throw new Error('The segment '+ match +' is required.');
} else {
return '';
}
},
str;

//TODO: extract this logic into pattern lexer and reuse TOKENS
str = this._pattern
.replace(/([:}]|\w(?=\/))\/?(:)/g, '$1__CR_OS__$2')
.replace(/\{([^}*]+)\*?\}/g, replaceFn)
.replace(/:([^:*]+)\*?:/g, replaceFn)
.replace(/(?:__CR_OS__)+$/, '') // remove trailing
.replace(/__CR_OS__/g, '/'); // add slash between segments

if (! this._matchRegexp.test(str) ) {
throw new Error('The generated string "'+ str +'" doesn\'t match the pattern "'+ this._pattern +'". Check supplied arguments.');
}
return str;
return crossroads.patternLexer.interpolate(this._pattern, replacements);
},

dispose : function () {
Expand Down
2 changes: 1 addition & 1 deletion dev/tests/spec/interpolate.spec.js
Expand Up @@ -44,7 +44,7 @@ describe('Route.interpolate()', function(){
var a = crossroads.addRoute('/{foo}/:bar:');
expect( function(){
a.interpolate({foo: 'lorem/ipsum', bar: 'dolor'});
}).toThrow( 'The generated string "/lorem/ipsum/dolor" doesn\'t match the pattern "/{foo}/:bar:". Check supplied arguments.' );
}).toThrow( 'Invalid value "lorem/ipsum" for segment "{foo}".' );
});

it('should throw an error if route was created by an RegExp pattern', function () {
Expand Down
2 changes: 1 addition & 1 deletion dev/tests/spec/parse.spec.js
Expand Up @@ -801,7 +801,7 @@ describe('crossroads.parse()', function(){
r.rules = {
a : ['news', 'article'],
b : /[\-0-9a-zA-Z]+/,
'c*' : ['foo/bar', 'edit', '123/456/789']
c : ['foo/bar', 'edit', '123/456/789']
};

r.matched.addOnce(function(a, b, c){
Expand Down
1 change: 1 addition & 0 deletions dev/tests/spec_runner-dist.html
Expand Up @@ -17,6 +17,7 @@
<script src="spec/create.spec.js"></script>
<script src="spec/signals.spec.js"></script>
<script src="spec/toString.spec.js"></script>
<script src="spec/interpolate.spec.js"></script>
<script src="spec/dispose.spec.js"></script>
</head>
<body>
Expand Down

0 comments on commit 625ee7e

Please sign in to comment.