Просмотр исходного кода

Add metrics and unit tests for error catching.

tags/0.5.0-vsc
Sahab Yazdani 15 лет назад
Родитель
Сommit
6aa535ce80
2 измененных файлов: 155 добавлений и 43 удалений
  1. +88
    -41
      mustache.js
  2. +67
    -2
      test/unit.js

+ 88
- 41
mustache.js Просмотреть файл

@@ -134,8 +134,24 @@ var Mustache = (function(undefined) {
return Object.prototype.toString.call(a) === '[object Array]'; return Object.prototype.toString.call(a) === '[object Array]';
} }
function create_error(line, character, message) {
return new Error('(' + line + ',' + character + '): ' + message);
function create_error(metrics, message) {
var str = '', err;
if (metrics) {
str = '(' + metrics.line + ',' + metrics.character + '): ';
if (metrics.partial) {
str = '[' + metrics.partial + ']' + str;
}
}
err = new Error(str + message);
if (metrics) {
err.line = metrics.line;
err.character = metrics.character;
err.partial = metrics.partial;
}
return err;
} }
/* END Helpers */ /* END Helpers */
@@ -145,7 +161,7 @@ var Mustache = (function(undefined) {
function compile(state, noReturn) { function compile(state, noReturn) {
var n, c, token; var n, c, token;
for (n = state.tokens.length;state.cursor<n;++state.cursor) {
for (n = state.tokens.length;state.cursor<n && !state.terminated;++state.cursor) {
token = state.tokens[state.cursor]; token = state.tokens[state.cursor];
if (token==='' || token===undefined) { if (token==='' || token===undefined) {
continue; continue;
@@ -163,13 +179,17 @@ var Mustache = (function(undefined) {
} }
if (is_newline(token)) { if (is_newline(token)) {
state.character = 1;
state.line++;
state.metrics.character = 1;
state.metrics.line++;
} else { } else {
state.character+=token.length;
state.metrics.character+=token.length;
} }
} }
if (state.parser === scan_section_parser && !state.terminated) {
throw create_error(state.metrics, 'Closing section tag "' + state.section.variable + '" expected.');
}
if (!noReturn) { if (!noReturn) {
var codeList = state.code; var codeList = state.code;
if (codeList.length === 0) { if (codeList.length === 0) {
@@ -187,7 +207,7 @@ var Mustache = (function(undefined) {
} }
var default_tokenizer = /(\r?\n)|({{![\s\S]*?!}})|({{[#\^\/&>]?\s*[^!{=]\S*?\s*}})|({{{\s*\S*?\s*}}})|({{=\S*?\s*\S*?=}})/; var default_tokenizer = /(\r?\n)|({{![\s\S]*?!}})|({{[#\^\/&>]?\s*[^!{=]\S*?\s*}})|({{{\s*\S*?\s*}}})|({{=\S*?\s*\S*?=}})/;
function create_compiler_state(template, partials, openTag, closeTag) {
function create_compiler_state(template, partials, openTag, closeTag, parse_pragma) {
openTag = openTag || '{{'; openTag = openTag || '{{';
closeTag = closeTag || '}}'; closeTag = closeTag || '}}';


@@ -208,9 +228,12 @@ var Mustache = (function(undefined) {
tokenizer = new RegExp(parts.join('|')); tokenizer = new RegExp(parts.join('|'));
} }


var code = [];
var state = {
line: 1, character: 1
var code = [], state = {
metrics: {
partial: null
, line: 1
, character: 1
}
, template: template || '' , template: template || ''
, partials: partials || {} , partials: partials || {}
, openTag: openTag , openTag: openTag
@@ -222,8 +245,10 @@ var Mustache = (function(undefined) {
code.push(f); code.push(f);
} }
}; };
pragmas(state); // use pragmas to determine parsing behaviour
if (parse_pragma!==false) { // explicit check, by default, look for pragma
pragmas(state); // use pragmas to determine parsing behaviour
}
// tokenize and initialize a cursor // tokenize and initialize a cursor
state.tokens = splitFunc.call(state.template, tokenizer); state.tokens = splitFunc.call(state.template, tokenizer);
@@ -266,7 +291,7 @@ var Mustache = (function(undefined) {
for (i=0, n=optionPairs.length; i<n; ++i) { for (i=0, n=optionPairs.length; i<n; ++i) {
scratch = optionPairs[i].split('='); scratch = optionPairs[i].split('=');
if (scratch.length !== 2) { if (scratch.length !== 2) {
throw create_error(state.line, state.character, 'Malformed pragma option "' + optionPairs[i] + '".');
throw create_error(undefined, 'Malformed pragma option "' + optionPairs[i] + '".');
} }
options[scratch[0]] = scratch[1]; options[scratch[0]] = scratch[1];
} }
@@ -275,7 +300,7 @@ var Mustache = (function(undefined) {
if (is_function(directives[pragma])) { if (is_function(directives[pragma])) {
directives[pragma](options); directives[pragma](options);
} else { } else {
throw create_error(state.line, state.character, 'This implementation of mustache does not implement the "' + pragma + '" pragma.');
throw create_error(undefined, 'This implementation of mustache does not implement the "' + pragma + '" pragma.');
} }


return ''; // blank out all pragmas return ''; // blank out all pragmas
@@ -360,7 +385,7 @@ var Mustache = (function(undefined) {
template, program; template, program;
if (!state.partials[variable]) { if (!state.partials[variable]) {
throw create_error(state.line, state.character, 'Unknown partial "' + variable + '".');
throw create_error(state.metrics, 'Unknown partial "' + variable + '".');
} }
if (!is_function(state.partials[variable])) { if (!is_function(state.partials[variable])) {
@@ -369,10 +394,12 @@ var Mustache = (function(undefined) {
template = state.partials[variable]; // remember what the partial was template = state.partials[variable]; // remember what the partial was
state.partials[variable] = noop; // avoid infinite recursion state.partials[variable] = noop; // avoid infinite recursion
program = compile(create_compiler_state(
var new_state = create_compiler_state(
template template
, state.partials
));
, state.partials
);
new_state.metrics.partial = variable;
program = compile(new_state);
state.partials[variable] = function(context, send_func) { state.partials[variable] = function(context, send_func) {
var value = find_in_stack(variable, context); var value = find_in_stack(variable, context);
@@ -412,10 +439,11 @@ var Mustache = (function(undefined) {
} }
var s = state.section, template = s.template_buffer.join(''), var s = state.section, template = s.template_buffer.join(''),
program = compile(create_compiler_state(template,
state.partials,
state.openTag,
state.closeTag));
program,
new_state = create_compiler_state(template, state.partials, state.openTag, state.closeTag, false);
new_state.metrics = s.metrics;
program = compile(new_state);
if (s.inverted) { if (s.inverted) {
state.send_code_func((function(program, variable){ return function(context, send_func) { state.send_code_func((function(program, variable){ return function(context, send_func) {
@@ -445,7 +473,9 @@ var Mustache = (function(undefined) {
var o = [], var o = [],
user_send_func = function(str) { o.push(str); }; user_send_func = function(str) { o.push(str); };
compile(create_compiler_state(hosFragment, partials))(context, user_send_func);
var new_state = create_compiler_state(hosFragment, partials);
new_state.metrics.partial = 'HOS@@anon';
compile(new_state)(context, user_send_func);
return o.join(''); return o.join('');
})); }));
@@ -462,7 +492,7 @@ var Mustache = (function(undefined) {
'!': noop, '!': noop,
'#': begin_section, '#': begin_section,
'^': begin_section, '^': begin_section,
'/': function(state, token) { throw create_error(state.line, state.character, 'Unbalanced End Section tag "' + token + '".'); },
'/': function(state, token) { throw create_error(state.metrics, 'Unbalanced End Section tag "' + token + '".'); },
'&': interpolate, '&': interpolate,
'{': interpolate, '{': interpolate,
'>': partial, '>': partial,
@@ -485,40 +515,49 @@ var Mustache = (function(undefined) {
}; };
function get_variable_name(state, token, prefix, postfix) { function get_variable_name(state, token, prefix, postfix) {
var fragment = token
.substring(
state.openTag.length + (prefix ? 1 : 0)
, token.length - state.closeTag.length - (postfix ? 1 : 0)
);
var fragment = token.substring(
state.openTag.length + (prefix ? 1 : 0)
, token.length - state.closeTag.length - (postfix ? 1 : 0)
);
if (String.prototype.trim) { if (String.prototype.trim) {
return fragment.trim();
fragment = fragment.trim();
} else { } else {
return fragment.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
fragment = fragment.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
if (fragment.indexOf(' ')!==-1) {
throw create_error(state.metrics, 'Malformed variable name "' + fragment + '".');
} }
return fragment;
} }
function change_delimiter(state, token) { function change_delimiter(state, token) {
var matches = token.match(new RegExp(escape_regex(state.openTag) + '=(\\S*?)\\s*(\\S*?)=' + escape_regex(state.closeTag))); var matches = token.match(new RegExp(escape_regex(state.openTag) + '=(\\S*?)\\s*(\\S*?)=' + escape_regex(state.closeTag)));


if ((matches || []).length!==3) { if ((matches || []).length!==3) {
throw create_error(state.line, state.character, 'Malformed change delimiter token: "' + token + '".');
throw create_error(state.metrics, 'Malformed change delimiter token "' + token + '".');
} }
var new_state = create_compiler_state( var new_state = create_compiler_state(
state.tokens.slice(state.cursor+1).join('') state.tokens.slice(state.cursor+1).join('')
, state.partials , state.partials
, matches[1] , matches[1]
, matches[2]);
, matches[2]
, false);
new_state.code = state.code; new_state.code = state.code;
new_state.send_code_func = state.send_code_func; new_state.send_code_func = state.send_code_func;
new_state.parser = state.parser; new_state.parser = state.parser;
new_state.metrics.line = state.metrics.line;
new_state.metrics.character = state.metrics.character + token.length;
new_state.metrics.partial = state.metrics.partial;
new_state.section = state.section; new_state.section = state.section;
if (new_state.section) { if (new_state.section) {
new_state.section.template_buffer.push(token); new_state.section.template_buffer.push(token);
} }
state.cursor = state.tokens.length; // finish off this level
state.terminated = true; // finish off this level
compile(new_state, true); compile(new_state, true);
} }
@@ -534,6 +573,11 @@ var Mustache = (function(undefined) {
, template_buffer: [] , template_buffer: []
, inverted: inverted , inverted: inverted
, child_sections: [] , child_sections: []
, metrics: {
partial: state.metrics.partial
, line: state.metrics.line
, character: state.metrics.character + token.length
}
}; };
} else { } else {
state.section.child_sections.push(variable); state.section.child_sections.push(variable);
@@ -548,17 +592,20 @@ var Mustache = (function(undefined) {
function end_section(state, token) { function end_section(state, token) {
var variable = get_variable_name(state, token, true); var variable = get_variable_name(state, token, true);
if (state.section.child_sections.length > 0 &&
state.section.child_sections[state.section.child_sections.length-1] === variable) {
state.section.child_sections.pop();
state.section.template_buffer.push(token);
if (state.section.child_sections.length > 0) {
var child_section = state.section.child_sections[state.section.child_sections.length-1];
if (child_section === variable) {
state.section.child_sections.pop();
state.section.template_buffer.push(token);
} else {
throw create_error(state.metrics, 'Unexpected section end tag "' + variable + '", expected "' + child_section + '".');
}
} else if (state.section.variable===variable) { } else if (state.section.variable===variable) {
section(state); section(state);
delete state.section; delete state.section;
state.parser = default_parser; state.parser = default_parser;
} else { } else {
throw create_error(state.line, state.character, 'Unexpected section end tag "' + variable + '", expected "' + state.section.variable + '".');
throw create_error(state.metrics, 'Unexpected section end tag "' + variable + '", expected "' + state.section.variable + '".');
} }
} }


+ 67
- 2
test/unit.js Просмотреть файл

@@ -52,11 +52,22 @@ test("Parser", function() {
{} {}
); );
}, function(e) { }, function(e) {
return e.message === '(1,1): Malformed change delimiter token: "{{=tag1}}".';
return e.message === '(1,1): Malformed change delimiter token "{{=tag1}}".';
}, },
'Malformed tags should be handled correctly.' 'Malformed tags should be handled correctly.'
); );
raises(
function() {
Mustache.to_html(
'{{tag blah}}'
)
}, function(e) {
return e.message === '(1,1): Malformed variable name "tag blah".';
},
'Malformed tags should be handled correctly.'
);
var partials = { 'partial' : '{{key}}' }; var partials = { 'partial' : '{{key}}' };
Mustache.compile('{{>partial}}', partials ); Mustache.compile('{{>partial}}', partials );
@@ -477,7 +488,7 @@ test("'%' (Pragmas)", function() {
); );
}, },
function(e) { function(e) {
return e.message === '(1,1): This implementation of mustache does not implement the "I-HAVE-THE-GREATEST-MUSTACHE" pragma.';
return e.message === 'This implementation of mustache does not implement the "I-HAVE-THE-GREATEST-MUSTACHE" pragma.';
}, },
'Notification of unimplemented pragmas' 'Notification of unimplemented pragmas'
); );
@@ -590,6 +601,60 @@ test("Demo", function() {
); );
}); });


test("Error Handling", function() {
raises(
function() {
Mustache.to_html(
'this is a partial\nyes it is. {{>partial}}',
{},
{partal: ''}
);
}, function(e) {
return e.line === 2 && e.character === 12;
},
'Missing partial line and character correctness.'
);
raises(
function() {
Mustache.to_html(
'this is a partial\nyes it is. {{>partial}}',
{},
{partial: 'error in {{#foobar}}'}
);
}, function(e) {
return e.message === '[partial](1,21): Closing section tag "foobar" expected.'
},
'Unbalanced section correctness (Part 1).'
);
raises(
function() {
Mustache.to_html(
'something something something {{/darkside}}.',
{},
{}
);
}, function(e) {
return e.message === '(1,31): Unbalanced End Section tag "{{/darkside}}".'
},
'Unbalanced section correctness (Part 2).'
);

raises(
function() {
Mustache.to_html(
'this is a partial\nyes it is. {{>maria}}',
{},
{maria: 'error in {{#foobar}}this is the most aw\ns\nme think {{#evar}}hello joe{{/foobar}}{{/evar}}'}
);
}, function(e) {
return e.message === '[maria](3,28): Unexpected section end tag "foobar", expected "evar".'
},
'Partials metric correctness.'
);
});

test("Regression Suite", function() { test("Regression Suite", function() {
// matches bug_11_eating_whitespace.html // matches bug_11_eating_whitespace.html
equals( equals(


Загрузка…
Отмена
Сохранить