зеркало из https://github.com/mozilla/djangode.git
Added a couple of filters
This commit is contained in:
Родитель
5e764095ba
Коммит
0b8c50ceea
|
@ -1,16 +1,22 @@
|
|||
#!/usr/bin/python
|
||||
import re, os
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
print ""
|
||||
|
||||
cmd = 'find . -name "*.test.js"';
|
||||
files = [file.rstrip('\n') for file in os.popen(cmd).readlines()]
|
||||
|
||||
files = Popen(cmd, shell=True, stdout=PIPE).communicate()[0].splitlines()
|
||||
failed_list = []
|
||||
|
||||
for file in files:
|
||||
output = os.popen('node ' + file).readlines()
|
||||
result = [line.rstrip('\n') for line in output if line.startswith('Total')][0]
|
||||
output = Popen('node ' + file, shell=True, stdout=PIPE).communicate()[0].splitlines()
|
||||
try:
|
||||
result = [line for line in output if line.startswith('Total')][0]
|
||||
except:
|
||||
#bizarre, but sometimes popen apears to return empty strings
|
||||
#I'm too tired to fix this right now, so for now just retry and hope for better results
|
||||
output = Popen('node ' + file, shell=True, stdout=PIPE).communicate()[0].splitlines()
|
||||
result = [line for line in output if line.startswith('Total')][0]
|
||||
(total, failed, error) = re.split(r':|,', result)[1::2]
|
||||
|
||||
if int(failed) > 0 or int(error) > 0:
|
||||
|
|
|
@ -7,6 +7,19 @@ var utils = require('utils/utils');
|
|||
var template_defaults = require('template/template_defaults');
|
||||
var template_loader = require('template/loader');
|
||||
|
||||
/***************** TOKEN **********************************/
|
||||
|
||||
function Token(type, contents) {
|
||||
this.type = type;
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
process.mixin(Token.prototype, {
|
||||
split_contents: function () {
|
||||
return utils.string.smart_split(this.contents);
|
||||
}
|
||||
});
|
||||
|
||||
/***************** TOKENIZER ******************************/
|
||||
|
||||
function tokenize(input) {
|
||||
|
@ -33,7 +46,7 @@ function tokenize(input) {
|
|||
function literal() {
|
||||
var res = consume_until("{{", "{%");
|
||||
|
||||
if (res[0]) { token_list.push( {type: 'text', contents: res[0] } ); }
|
||||
if (res[0]) { token_list.push( new Token('text', res[0]) ) }
|
||||
|
||||
if (res[1] === "{{") { return variable_tag; }
|
||||
if (res[1] === "{%") { return template_tag; }
|
||||
|
@ -43,8 +56,7 @@ function tokenize(input) {
|
|||
function variable_tag() {
|
||||
var res = consume_until("}}");
|
||||
|
||||
if (res[0]) { token_list.push( {type: 'variable', contents: res[0].trim() } ); }
|
||||
|
||||
if (res[0]) { token_list.push( new Token('variable', res[0].trim()) ) }
|
||||
if (res[1]) { return literal; }
|
||||
return undefined;
|
||||
}
|
||||
|
@ -53,7 +65,7 @@ function tokenize(input) {
|
|||
var res = consume_until("%}"),
|
||||
parts = res[0].trim().split(/\s/, 1);
|
||||
|
||||
token_list.push( { type: parts[0], contents: res[0].trim() });
|
||||
token_list.push( new Token(parts[0], res[0].trim()) );
|
||||
|
||||
if (res[1]) { return literal; }
|
||||
return undefined;
|
||||
|
@ -68,152 +80,6 @@ function tokenize(input) {
|
|||
return token_list;
|
||||
}
|
||||
|
||||
|
||||
/*********** PARSER **********************************/
|
||||
|
||||
function Parser(input) {
|
||||
this.token_list = tokenize(input);
|
||||
this.indent = 0;
|
||||
this.blocks = {};
|
||||
}
|
||||
|
||||
function parser_error(e) {
|
||||
return 'Parsing exception: ' + JSON.stringify(e, 0, 2);
|
||||
}
|
||||
|
||||
function make_nodelist() {
|
||||
var node_list = [];
|
||||
node_list.evaluate = function (context) {
|
||||
return this.reduce( function (p, c) { return p + c(context); }, '');
|
||||
};
|
||||
node_list.only_types = function (/*args*/) {
|
||||
var args = Array.prototype.slice.apply(arguments);
|
||||
return this.filter( function (x) { return args.indexOf(x.type) > -1; } );
|
||||
};
|
||||
node_list.append = function (node, type) {
|
||||
node.type = type;
|
||||
this.push(node);
|
||||
};
|
||||
return node_list;
|
||||
}
|
||||
|
||||
process.mixin(Parser.prototype, {
|
||||
|
||||
callbacks: template_defaults.callbacks,
|
||||
|
||||
parse: function () {
|
||||
|
||||
var stoppers = Array.prototype.slice.apply(arguments);
|
||||
var node_list = make_nodelist();
|
||||
var token = this.token_list[0];
|
||||
var callback = null;
|
||||
|
||||
//sys.debug('' + this.indent++ + ':starting parsing with stoppers ' + stoppers.join(', '));
|
||||
|
||||
while (this.token_list.length) {
|
||||
if (stoppers.indexOf(this.token_list[0].type) > -1) {
|
||||
//sys.debug('' + this.indent-- + ':parse done returning at ' + token[0] + ' (length: ' + node_list.length + ')');
|
||||
return node_list;
|
||||
}
|
||||
|
||||
token = this.next_token();
|
||||
|
||||
//sys.debug('' + this.indent + ': ' + token);
|
||||
|
||||
callback = this.callbacks[token.type];
|
||||
if (callback && typeof callback === 'function') {
|
||||
node_list.append( callback(this, token), token.type );
|
||||
} else {
|
||||
//throw parser_error('Unknown tag: ' + token[0]);
|
||||
node_list.append(
|
||||
template_defaults.nodes.TextNode('[[ UNKNOWN ' + token.type + ' ]]'),
|
||||
'UNKNOWN'
|
||||
);
|
||||
}
|
||||
}
|
||||
if (stoppers.length) {
|
||||
throw new parser_error('Tag not found: ' + stoppers.join(', '));
|
||||
}
|
||||
|
||||
//sys.debug('' + this.indent-- + ':parse done returning end (length: ' + node_list.length + ')');
|
||||
|
||||
return node_list;
|
||||
},
|
||||
|
||||
next_token: function () {
|
||||
return this.token_list.shift();
|
||||
},
|
||||
|
||||
delete_first_token: function () {
|
||||
this.token_list.shift();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function normalize(value) {
|
||||
if (typeof value !== 'string') { return value; }
|
||||
|
||||
if (value === 'true') { return true; }
|
||||
if (value === 'false') { return false; }
|
||||
if (/^\d/.exec(value)) { return value - 0; }
|
||||
|
||||
var isStringLiteral = /^(["'])(.*?)\1$/.exec(value);
|
||||
if (isStringLiteral) { return isStringLiteral.pop(); }
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*************** Context *********************************/
|
||||
|
||||
function Context(o) {
|
||||
this.scope = [ o || {} ];
|
||||
this.blocks = {};
|
||||
this.autoescaping = true;
|
||||
}
|
||||
|
||||
process.mixin(Context.prototype, {
|
||||
get: function (name) {
|
||||
|
||||
var normalized = normalize(name);
|
||||
if (name !== normalized) { return normalized; }
|
||||
|
||||
var parts = name.split('.');
|
||||
name = parts.shift();
|
||||
|
||||
var val, level, next;
|
||||
for (level = 0; level < this.scope.length; level++) {
|
||||
if (this.scope[level].hasOwnProperty(name)) {
|
||||
val = this.scope[level][name];
|
||||
while (parts.length && val) {
|
||||
next = val[parts.shift()];
|
||||
if (typeof next === 'function') {
|
||||
val = next.apply(val);
|
||||
} else {
|
||||
val = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof val === 'function') {
|
||||
return val();
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
set: function (name, value) {
|
||||
this.scope[0][name] = value;
|
||||
},
|
||||
push: function (o) {
|
||||
this.scope.unshift(o || {});
|
||||
},
|
||||
pop: function () {
|
||||
return this.scope.shift();
|
||||
},
|
||||
});
|
||||
|
||||
/*********** FilterExpression **************************/
|
||||
|
||||
var FilterExpression = function (expression, constant) {
|
||||
|
@ -307,7 +173,155 @@ process.mixin(FilterExpression.prototype, {
|
|||
}
|
||||
});
|
||||
|
||||
exports.FilterExpression = FilterExpression;
|
||||
/*********** PARSER **********************************/
|
||||
|
||||
function Parser(input) {
|
||||
this.token_list = tokenize(input);
|
||||
this.indent = 0;
|
||||
this.blocks = {};
|
||||
}
|
||||
|
||||
function parser_error(e) {
|
||||
return 'Parsing exception: ' + JSON.stringify(e, 0, 2);
|
||||
}
|
||||
|
||||
function make_nodelist() {
|
||||
var node_list = [];
|
||||
node_list.evaluate = function (context) {
|
||||
return this.reduce( function (p, c) { return p + c(context); }, '');
|
||||
};
|
||||
node_list.only_types = function (/*args*/) {
|
||||
var args = Array.prototype.slice.apply(arguments);
|
||||
return this.filter( function (x) { return args.indexOf(x.type) > -1; } );
|
||||
};
|
||||
node_list.append = function (node, type) {
|
||||
node.type = type;
|
||||
this.push(node);
|
||||
};
|
||||
return node_list;
|
||||
}
|
||||
|
||||
process.mixin(Parser.prototype, {
|
||||
|
||||
callbacks: template_defaults.callbacks,
|
||||
|
||||
parse: function () {
|
||||
|
||||
var stoppers = Array.prototype.slice.apply(arguments);
|
||||
var node_list = make_nodelist();
|
||||
var token = this.token_list[0];
|
||||
var callback = null;
|
||||
|
||||
//sys.debug('' + this.indent++ + ':starting parsing with stoppers ' + stoppers.join(', '));
|
||||
|
||||
while (this.token_list.length) {
|
||||
if (stoppers.indexOf(this.token_list[0].type) > -1) {
|
||||
//sys.debug('' + this.indent-- + ':parse done returning at ' + token[0] + ' (length: ' + node_list.length + ')');
|
||||
return node_list;
|
||||
}
|
||||
|
||||
token = this.next_token();
|
||||
|
||||
//sys.debug('' + this.indent + ': ' + token);
|
||||
|
||||
callback = this.callbacks[token.type];
|
||||
if (callback && typeof callback === 'function') {
|
||||
node_list.append( callback(this, token), token.type );
|
||||
} else {
|
||||
//throw parser_error('Unknown tag: ' + token[0]);
|
||||
node_list.append(
|
||||
template_defaults.nodes.TextNode('[[ UNKNOWN ' + token.type + ' ]]'),
|
||||
'UNKNOWN'
|
||||
);
|
||||
}
|
||||
}
|
||||
if (stoppers.length) {
|
||||
throw new parser_error('Tag not found: ' + stoppers.join(', '));
|
||||
}
|
||||
|
||||
//sys.debug('' + this.indent-- + ':parse done returning end (length: ' + node_list.length + ')');
|
||||
|
||||
return node_list;
|
||||
},
|
||||
|
||||
next_token: function () {
|
||||
return this.token_list.shift();
|
||||
},
|
||||
|
||||
delete_first_token: function () {
|
||||
this.token_list.shift();
|
||||
},
|
||||
|
||||
make_filterexpression: function (expression, constant) {
|
||||
return new FilterExpression(expression, constant);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function normalize(value) {
|
||||
if (typeof value !== 'string') { return value; }
|
||||
|
||||
if (value === 'true') { return true; }
|
||||
if (value === 'false') { return false; }
|
||||
if (/^\d/.exec(value)) { return value - 0; }
|
||||
|
||||
var isStringLiteral = /^(["'])(.*?)\1$/.exec(value);
|
||||
if (isStringLiteral) { return isStringLiteral.pop(); }
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*************** Context *********************************/
|
||||
|
||||
function Context(o) {
|
||||
this.scope = [ o || {} ];
|
||||
this.extends = '';
|
||||
this.blocks = {};
|
||||
this.autoescaping = true;
|
||||
}
|
||||
|
||||
process.mixin(Context.prototype, {
|
||||
get: function (name) {
|
||||
|
||||
var normalized = normalize(name);
|
||||
if (name !== normalized) { return normalized; }
|
||||
|
||||
var parts = name.split('.');
|
||||
name = parts.shift();
|
||||
|
||||
var val, level, next;
|
||||
for (level = 0; level < this.scope.length; level++) {
|
||||
if (this.scope[level].hasOwnProperty(name)) {
|
||||
val = this.scope[level][name];
|
||||
while (parts.length && val) {
|
||||
next = val[parts.shift()];
|
||||
if (typeof next === 'function') {
|
||||
val = next.apply(val);
|
||||
} else {
|
||||
val = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof val === 'function') {
|
||||
return val();
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
set: function (name, value) {
|
||||
this.scope[0][name] = value;
|
||||
},
|
||||
push: function (o) {
|
||||
this.scope.unshift(o || {});
|
||||
},
|
||||
pop: function () {
|
||||
return this.scope.shift();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/*********** Template **********************************/
|
||||
|
@ -321,7 +335,7 @@ process.mixin(Template.prototype, {
|
|||
render: function (o) {
|
||||
|
||||
var context = (o instanceof Context) ? o : new Context(o || {});
|
||||
context.extends = false;
|
||||
context.extends = '';
|
||||
|
||||
var rendered = this.node_list.evaluate(context);
|
||||
|
||||
|
@ -342,16 +356,12 @@ exports.parse = function (input) {
|
|||
return new Template(input);
|
||||
};
|
||||
|
||||
// TODO: Make this a property on a token class
|
||||
function split_token(input) {
|
||||
return utils.string.smart_split(input);
|
||||
}
|
||||
exports.split_token = split_token;
|
||||
|
||||
|
||||
|
||||
// exported for test
|
||||
exports.Context = Context;
|
||||
exports.FilterExpression = FilterExpression;
|
||||
exports.tokenize = tokenize;
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -14,15 +14,15 @@ testcase('Test tokenizer');
|
|||
test('split token contents', function () {
|
||||
assertEquals(
|
||||
['virker', 'det', 'her'],
|
||||
split_token(' virker det her ')
|
||||
tokenize(' virker det her ')[0].split_contents()
|
||||
);
|
||||
assertEquals(
|
||||
['her', 'er', '"noget der er i qoutes"', 'og', 'noget', 'der', 'ikke', 'er'],
|
||||
split_token('her er "noget der er i qoutes" og noget der ikke er')
|
||||
tokenize('her er "noget der er i qoutes" og noget der ikke er')[0].split_contents()
|
||||
);
|
||||
|
||||
assertEquals( ['date:"F j, Y"'], split_token('date:"F j, Y"'));
|
||||
assertEquals( ['date:', '"F j, Y"'], split_token('date: "F j, Y"'));
|
||||
assertEquals( ['date:"F j, Y"'], tokenize('date:"F j, Y"')[0].split_contents());
|
||||
assertEquals( ['date:', '"F j, Y"'], tokenize('date: "F j, Y"')[0].split_contents());
|
||||
});
|
||||
|
||||
testcase('Filter Expression tests');
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
/*global require, process, exports, escape */
|
||||
|
||||
var sys = require('sys');
|
||||
|
||||
var template = require('template/template');
|
||||
var utils = require('utils/utils');
|
||||
|
||||
/* TODO: Missing filters
|
||||
|
@ -15,7 +13,6 @@ var utils = require('utils/utils');
|
|||
time
|
||||
timesince
|
||||
timeuntil
|
||||
truncatewords_html
|
||||
unordered_list
|
||||
urlize
|
||||
urlizetrunc
|
||||
|
@ -35,7 +32,7 @@ Missing tags:
|
|||
load
|
||||
|
||||
debug
|
||||
firstof
|
||||
|
||||
ifchanged
|
||||
ifequal
|
||||
ifnotequal
|
||||
|
@ -45,7 +42,6 @@ Missing tags:
|
|||
templatetag
|
||||
url
|
||||
widthratio
|
||||
with
|
||||
|
||||
NOTE:
|
||||
cycle tag does not support legacy syntax (row1,row2,row3)
|
||||
|
@ -129,24 +125,19 @@ var filters = exports.filters = {
|
|||
length: function (value, arg) { return value.length ? value.length : 0; },
|
||||
length_is: function (value, arg) { return value.length === arg; },
|
||||
linebreaks: function (value, arg, safety) {
|
||||
if (!safety.is_safe && safety.must_escape) {
|
||||
value = utils.html.escape("" + value);
|
||||
}
|
||||
var out = utils.html.linebreaks("" + value, { escape: !safety.is_safe && safety.must_escape });
|
||||
safety.is_safe = true;
|
||||
return utils.html.linebreaks("" + value);
|
||||
return out;
|
||||
},
|
||||
linebreaksbr: function (value, arg, safety) {
|
||||
if (!safety.is_safe && safety.must_escape) {
|
||||
value = utils.html.escape("" + value);
|
||||
}
|
||||
var out = utils.html.linebreaks("" + value, { onlybr: true, escape: !safety.is_safe && safety.must_escape });
|
||||
safety.is_safe = true;
|
||||
return "" + value.replace(/\n/g, '<br />');
|
||||
return out;
|
||||
},
|
||||
linenumbers: function (value, arg, safety) {
|
||||
var lines = String(value).split('\n');
|
||||
var len = String(lines.length).length;
|
||||
|
||||
// TODO: escape if string is not safe, and autoescaping is active
|
||||
var out = lines
|
||||
.map(function (s, idx) {
|
||||
if (!safety.is_safe && safety.must_escape) {
|
||||
|
@ -241,6 +232,10 @@ var filters = exports.filters = {
|
|||
truncatewords: function (value, arg) {
|
||||
return String(value).split(/\s+/g).slice(0, arg).join(' ') + ' ...';
|
||||
},
|
||||
truncatewords_html: function (value, arg, safety) {
|
||||
safety.is_safe = true;
|
||||
return utils.html.truncate_html_words(value, arg);
|
||||
},
|
||||
upper: function (value, arg) {
|
||||
return (value + '').toUpperCase();
|
||||
},
|
||||
|
@ -384,6 +379,28 @@ var nodes = exports.nodes = {
|
|||
context.autoescaping = before;
|
||||
return out;
|
||||
}
|
||||
},
|
||||
|
||||
FirstOfNode: function (choices) {
|
||||
return function (context) {
|
||||
var i, val;
|
||||
for (i = 0; i < choices.length; i++) {
|
||||
var val = context.get(choices[i]);
|
||||
if (val) { return val; }
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
WithNode: function (variable, name, node_list) {
|
||||
return function (context) {
|
||||
var item = context.get(variable);
|
||||
context.push();
|
||||
context.set(name, item);
|
||||
var out = node_list.evaluate( context );
|
||||
context.pop();
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -392,7 +409,7 @@ var callbacks = exports.callbacks = {
|
|||
'text': function (parser, token) { return nodes.TextNode(token.contents); },
|
||||
|
||||
'variable': function (parser, token) {
|
||||
return nodes.VariableNode( new template.FilterExpression(token.contents) );
|
||||
return nodes.VariableNode( parser.make_filterexpression(token.contents) );
|
||||
},
|
||||
|
||||
'comment': function (parser, token) {
|
||||
|
@ -403,7 +420,7 @@ var callbacks = exports.callbacks = {
|
|||
|
||||
'for': function (parser, token) {
|
||||
|
||||
var parts = template.split_token(token.contents);
|
||||
var parts = token.split_contents();
|
||||
|
||||
if (parts[0] !== 'for' || parts[2] !== 'in' || (parts[4] && parts[4] !== 'reversed')) {
|
||||
throw 'unexpected syntax in "for" tag: ' + token.contents;
|
||||
|
@ -421,7 +438,7 @@ var callbacks = exports.callbacks = {
|
|||
|
||||
'if': function (parser, token) {
|
||||
|
||||
var parts = template.split_token( token.contents );
|
||||
var parts = token.split_contents();
|
||||
|
||||
if (parts[0] !== 'if') { throw 'unexpected syntax in "if" tag'; }
|
||||
|
||||
|
@ -464,7 +481,7 @@ var callbacks = exports.callbacks = {
|
|||
},
|
||||
|
||||
'cycle': function (parser, token) {
|
||||
var parts = template.split_token(token.contents);
|
||||
var parts = token.split_contents();
|
||||
|
||||
if (parts[0] !== 'cycle') { throw 'unexpected syntax in "cycle" tag'; }
|
||||
|
||||
|
@ -497,10 +514,10 @@ var callbacks = exports.callbacks = {
|
|||
},
|
||||
|
||||
'filter': function (parser, token) {
|
||||
var parts = template.split_token(token.contents);
|
||||
var parts = token.split_contents();
|
||||
if (parts[0] !== 'filter' || parts.length > 2) { throw 'unexpected syntax in "filter" tag'; }
|
||||
|
||||
var expr = new template.FilterExpression('|' + parts[1], ' ');
|
||||
var expr = parser.make_filterexpression('|' + parts[1], ' ');
|
||||
|
||||
var node_list = parser.parse('endfilter');
|
||||
parser.delete_first_token();
|
||||
|
@ -509,7 +526,7 @@ var callbacks = exports.callbacks = {
|
|||
},
|
||||
|
||||
'block': function (parser, token) {
|
||||
var parts = template.split_token(token.contents);
|
||||
var parts = token.split_contents();
|
||||
if (parts[0] !== 'block' || parts.length !== 2) { throw 'unexpected syntax in "block" tag'; }
|
||||
var name = parts[1];
|
||||
|
||||
|
@ -520,7 +537,7 @@ var callbacks = exports.callbacks = {
|
|||
},
|
||||
|
||||
'extends': function (parser, token) {
|
||||
var parts = template.split_token(token.contents);
|
||||
var parts = token.split_contents();
|
||||
if (parts[0] !== 'extends' || parts.length !== 2) { throw 'unexpected syntax in "extends" tag'; }
|
||||
var name = parts[1];
|
||||
|
||||
|
@ -528,7 +545,7 @@ var callbacks = exports.callbacks = {
|
|||
},
|
||||
|
||||
'autoescape': function (parser, token) {
|
||||
var parts = template.split_token(token.contents);
|
||||
var parts = token.split_contents();
|
||||
if (parts[0] !== 'autoescape' || parts.length !== 2) { throw 'unexpected syntax in "autoescape" tag'; }
|
||||
var enable;
|
||||
if (parts[1] === 'on') {
|
||||
|
@ -543,6 +560,23 @@ var callbacks = exports.callbacks = {
|
|||
parser.delete_first_token();
|
||||
|
||||
return nodes.AutoescapeNode(enable, node_list);
|
||||
},
|
||||
|
||||
'firstof': function (parser, token) {
|
||||
var parts = token.split_contents();
|
||||
if (parts[0] !== 'firstof') { throw 'unexpected syntax in "firstof" tag'; }
|
||||
return nodes.FirstOfNode( parts.slice(1) );
|
||||
},
|
||||
|
||||
'with': function (parser, token) {
|
||||
var parts = token.split_contents();
|
||||
if (parts[0] !== 'with' || parts[2] !== 'as' || parts.length !== 4) {
|
||||
throw 'unexpected syntax in "with" tag';
|
||||
}
|
||||
var node_list = parser.parse('endwith');
|
||||
parser.delete_first_token();
|
||||
|
||||
return nodes.WithNode(parts[1], parts[3], node_list);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -160,6 +160,23 @@ testcase('autoescape')
|
|||
var t = template.parse('{% autoescape on %}{{ test }}{% endautoescape %}');
|
||||
assertEquals( '<script>', t.render( {test: '<script>'} ));
|
||||
});
|
||||
testcase('firstof')
|
||||
test('should parse and evaluate', function () {
|
||||
var t = template.parse('{% firstof var1 var2 var3 %}');
|
||||
assertEquals('hest', t.render( { var1: 'hest' } ));
|
||||
assertEquals('hest', t.render( { var2: 'hest' } ));
|
||||
assertEquals('', t.render());
|
||||
t = template.parse('{% firstof var1 var2 var3 "fallback" %}');
|
||||
assertEquals('fallback', t.render());
|
||||
});
|
||||
testcase('with')
|
||||
test('function result should be cached', function () {
|
||||
var t = template.parse('{% with test.sub.func as tmp %}{{ tmp }}:{{ tmp }}{% endwith %}');
|
||||
var cnt = 0;
|
||||
var o = { test: { sub: { func: function () { cnt++; return cnt; } } } }
|
||||
assertEquals('1:1', t.render(o));
|
||||
assertEquals(1, cnt);
|
||||
});
|
||||
|
||||
run();
|
||||
|
||||
|
|
|
@ -377,5 +377,15 @@ testcase('escape');
|
|||
filters.escape('hurra', null, safety);
|
||||
assertEquals(true, safety.must_escape);
|
||||
});
|
||||
testcase('truncatewords_html');
|
||||
test('should truncate and close tags', function () {
|
||||
assertEquals('Joel is ...', filters.truncatewords_html('Joel is a slug', 2, {}));
|
||||
assertEquals('<p>Joel is ...</p>', filters.truncatewords_html('<p>Joel is a slug</p>', 2, {}));
|
||||
});
|
||||
test('should mark output as safe', function () {
|
||||
var safety = {};
|
||||
filters.truncatewords_html('<p>Joel is a slug</p>', 2, safety);
|
||||
assertEquals(true, safety.is_safe);
|
||||
});
|
||||
run();
|
||||
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
|
||||
var sys = require('sys');
|
||||
|
||||
/* Function: escape(value);
|
||||
Escapes the characters &, <, >, ' and " in string with html entities.
|
||||
Arguments:
|
||||
value - string to escape
|
||||
*/
|
||||
var escape = exports.escape = function (value) {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
|
@ -10,7 +15,7 @@ var escape = exports.escape = function (value) {
|
|||
.replace(/>/g, '>')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/"/g, '&qout;');
|
||||
}
|
||||
};
|
||||
|
||||
/* Function: linebreaks(value, options);
|
||||
Converts newlines into <p> and <br />s.
|
||||
|
@ -18,17 +23,81 @@ var escape = exports.escape = function (value) {
|
|||
value - string, the string to convert.
|
||||
options - optional, see options
|
||||
Options:
|
||||
autoescape - boolean, if true pass the string through escape()
|
||||
escape - boolean, if true pass the string through escape()
|
||||
onlybr - boolean, if true only br tags will be created.
|
||||
*/
|
||||
var linebreaks = exports.linebreaks = function (value, options) {
|
||||
options = options || {};
|
||||
value = value.replace(/\r\n|\r|\n/g, '\n');
|
||||
|
||||
if (options.onlybr) {
|
||||
return (options.escape ? escape(value) : value).replace(/\n/g, '<br />');
|
||||
}
|
||||
|
||||
var lines = value.split(/\n{2,}/);
|
||||
if (options.autoescape) {
|
||||
if (options.escape) {
|
||||
lines = lines.map( function (x) { return '<p>' + escape(x).replace('\n', '<br />') + '</p>'; } );
|
||||
} else {
|
||||
lines = lines.map( function (x) { return '<p>' + x.replace('\n', '<br />') + '</p>'; } );
|
||||
}
|
||||
return lines.join('\n\n');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var re_words = /&.*?;|<.*?>|(\w[\w\-]*)/g;
|
||||
var re_tag = /<(\/)?([^ ]+?)(?: (\/)| .*?)?>/;
|
||||
var html4_singlets = ['br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input'];
|
||||
var truncate_html_words = exports.truncate_html_words = function (input, cnt) {
|
||||
var words = 0, pos = 0, elipsis_pos = 0, length = cnt - 0;
|
||||
var open_tags = [];
|
||||
|
||||
if (!length) { return ''; }
|
||||
|
||||
re_words.lastIndex = 0;
|
||||
|
||||
while (words <= length) {
|
||||
var m = re_words( input );
|
||||
if (!m) {
|
||||
// parsed through string
|
||||
break;
|
||||
}
|
||||
|
||||
pos = re_words.lastIndex;
|
||||
|
||||
if (m[1]) {
|
||||
// this is not a tag
|
||||
words += 1;
|
||||
if (words === length) {
|
||||
elipsis_pos = pos;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var tag = re_tag( m[0] );
|
||||
if (!tag || elipsis_pos) {
|
||||
// don't worry about non-tags or tags after truncate point
|
||||
continue;
|
||||
}
|
||||
|
||||
var closing_tag = tag[1], tagname = tag[2].toLowerCase(), self_closing = tag[3];
|
||||
if (self_closing || html4_singlets.indexOf(tagname) > -1) {
|
||||
continue;
|
||||
} else if (closing_tag) {
|
||||
var idx = open_tags.indexOf(tagname);
|
||||
if (idx > -1) {
|
||||
// SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags
|
||||
open_tags = open_tags.slice(idx + 1);
|
||||
}
|
||||
} else {
|
||||
open_tags.unshift( tagname );
|
||||
}
|
||||
}
|
||||
|
||||
if (words <= length) {
|
||||
return input;
|
||||
}
|
||||
return open_tags.reduce( function (p,c) { return p + '</' + c + '>'; }, input.slice(0, elipsis_pos) + ' ...');
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,14 @@ testcase('tests for linebreaks()')
|
|||
+ '\n'
|
||||
+ '<p>The days are just packed!<br /></p>';
|
||||
assertEquals(expected, linebreaks(input));
|
||||
assertEquals(expected_escaped, linebreaks(input, { autoescape: true }));
|
||||
assertEquals(expected_escaped, linebreaks(input, { escape: true }));
|
||||
})
|
||||
testcase('truncate_html_words');
|
||||
test('should truncate strings without tags', function () {
|
||||
assertEquals('Joel is ...', truncate_html_words('Joel is a slug', 2));
|
||||
});
|
||||
test('should close tags on truncate', function () {
|
||||
assertEquals('<p>Joel is ...</p>', truncate_html_words('<p>Joel is a slug</p>', 2));
|
||||
});
|
||||
|
||||
run();
|
||||
|
|
|
@ -102,6 +102,9 @@ function sprintf () {
|
|||
}
|
||||
/*************************************************************************/
|
||||
|
||||
exports.sprintf = sprintf;
|
||||
exports.str_repeat = str_repeat;
|
||||
|
||||
/*************************************************************************
|
||||
* titleCaps from http://ejohn.org/files/titleCaps.js (by John Resig)
|
||||
*/
|
||||
|
@ -151,10 +154,6 @@ exports.titleCaps = titleCaps;
|
|||
|
||||
/*************************************************************************/
|
||||
|
||||
exports.sprintf = sprintf;
|
||||
exports.str_repeat = str_repeat;
|
||||
|
||||
|
||||
function center(s, width) {
|
||||
if (s.length > width) { return s; }
|
||||
var right = Math.round((width - s.length) / 2);
|
||||
|
|
Загрузка…
Ссылка в новой задаче