1412 строки
34 KiB
JavaScript
1412 строки
34 KiB
JavaScript
// Browser bundle of nunjucks 1.0.0 (slim, only works with precompiled templates)
|
|
|
|
(function() {
|
|
var modules = {};
|
|
(function() {
|
|
|
|
// A simple class system, more documentation to come
|
|
|
|
function extend(cls, name, props) {
|
|
// This does that same thing as Object.create, but with support for IE8
|
|
var F = function() {};
|
|
F.prototype = cls.prototype;
|
|
var prototype = new F();
|
|
|
|
var fnTest = /xyz/.test(function(){ xyz; }) ? /\bparent\b/ : /.*/;
|
|
props = props || {};
|
|
|
|
for(var k in props) {
|
|
var src = props[k];
|
|
var parent = prototype[k];
|
|
|
|
if(typeof parent == "function" &&
|
|
typeof src == "function" &&
|
|
fnTest.test(src)) {
|
|
prototype[k] = (function (src, parent) {
|
|
return function() {
|
|
// Save the current parent method
|
|
var tmp = this.parent;
|
|
|
|
// Set parent to the previous method, call, and restore
|
|
this.parent = parent;
|
|
var res = src.apply(this, arguments);
|
|
this.parent = tmp;
|
|
|
|
return res;
|
|
};
|
|
})(src, parent);
|
|
}
|
|
else {
|
|
prototype[k] = src;
|
|
}
|
|
}
|
|
|
|
prototype.typename = name;
|
|
|
|
var new_cls = function() {
|
|
if(prototype.init) {
|
|
prototype.init.apply(this, arguments);
|
|
}
|
|
};
|
|
|
|
new_cls.prototype = prototype;
|
|
new_cls.prototype.constructor = new_cls;
|
|
|
|
new_cls.extend = function(name, props) {
|
|
if(typeof name == "object") {
|
|
props = name;
|
|
name = "anonymous";
|
|
}
|
|
return extend(new_cls, name, props);
|
|
};
|
|
|
|
return new_cls;
|
|
}
|
|
|
|
modules.object = extend(Object, "Object", {});
|
|
})();
|
|
(function() {
|
|
var ArrayProto = Array.prototype;
|
|
var ObjProto = Object.prototype;
|
|
|
|
var escapeMap = {
|
|
'&': '&',
|
|
'"': '"',
|
|
"'": ''',
|
|
"<": '<',
|
|
">": '>'
|
|
};
|
|
|
|
var lookupEscape = function(ch) {
|
|
return escapeMap[ch];
|
|
};
|
|
|
|
var exports = modules.lib = {};
|
|
|
|
exports.withPrettyErrors = function(path, withInternals, func) {
|
|
// try {
|
|
return func();
|
|
// } catch (e) {
|
|
// if (!e.Update) {
|
|
// // not one of ours, cast it
|
|
// e = new exports.TemplateError(e);
|
|
// }
|
|
// e.Update(path);
|
|
|
|
// // Unless they marked the dev flag, show them a trace from here
|
|
// if (!withInternals) {
|
|
// var old = e;
|
|
// e = new Error(old.message);
|
|
// e.name = old.name;
|
|
// }
|
|
|
|
// throw e;
|
|
// }
|
|
};
|
|
|
|
exports.TemplateError = function(message, lineno, colno) {
|
|
var err = this;
|
|
|
|
if (message instanceof Error) { // for casting regular js errors
|
|
err = message;
|
|
message = message.name + ": " + message.message;
|
|
} else {
|
|
if(Error.captureStackTrace) {
|
|
Error.captureStackTrace(err);
|
|
}
|
|
}
|
|
|
|
err.name = 'Template render error';
|
|
err.message = message;
|
|
err.lineno = lineno;
|
|
err.colno = colno;
|
|
err.firstUpdate = true;
|
|
|
|
err.Update = function(path) {
|
|
var message = "(" + (path || "unknown path") + ")";
|
|
|
|
// only show lineno + colno next to path of template
|
|
// where error occurred
|
|
if (this.firstUpdate) {
|
|
if(this.lineno && this.colno) {
|
|
message += ' [Line ' + this.lineno + ', Column ' + this.colno + ']';
|
|
}
|
|
else if(this.lineno) {
|
|
message += ' [Line ' + this.lineno + ']';
|
|
}
|
|
}
|
|
|
|
message += '\n ';
|
|
if (this.firstUpdate) {
|
|
message += ' ';
|
|
}
|
|
|
|
this.message = message + (this.message || '');
|
|
this.firstUpdate = false;
|
|
return this;
|
|
};
|
|
|
|
return err;
|
|
};
|
|
|
|
exports.TemplateError.prototype = Error.prototype;
|
|
|
|
exports.escape = function(val) {
|
|
return val.replace(/[&"'<>]/g, lookupEscape);
|
|
};
|
|
|
|
exports.isFunction = function(obj) {
|
|
return ObjProto.toString.call(obj) == '[object Function]';
|
|
};
|
|
|
|
exports.isArray = Array.isArray || function(obj) {
|
|
return ObjProto.toString.call(obj) == '[object Array]';
|
|
};
|
|
|
|
exports.isString = function(obj) {
|
|
return ObjProto.toString.call(obj) == '[object String]';
|
|
};
|
|
|
|
exports.isObject = function(obj) {
|
|
return obj === Object(obj);
|
|
};
|
|
|
|
exports.groupBy = function(obj, val) {
|
|
var result = {};
|
|
var iterator = exports.isFunction(val) ? val : function(obj) { return obj[val]; };
|
|
for(var i=0; i<obj.length; i++) {
|
|
var value = obj[i];
|
|
var key = iterator(value, i);
|
|
(result[key] || (result[key] = [])).push(value);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
exports.toArray = function(obj) {
|
|
return Array.prototype.slice.call(obj);
|
|
};
|
|
|
|
exports.without = function(array) {
|
|
var result = [];
|
|
if (!array) {
|
|
return result;
|
|
}
|
|
var index = -1,
|
|
length = array.length,
|
|
contains = exports.toArray(arguments).slice(1);
|
|
|
|
while(++index < length) {
|
|
if(contains.indexOf(array[index]) === -1) {
|
|
result.push(array[index]);
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
exports.extend = function(obj, obj2) {
|
|
for(var k in obj2) {
|
|
obj[k] = obj2[k];
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
exports.repeat = function(char_, n) {
|
|
var str = '';
|
|
for(var i=0; i<n; i++) {
|
|
str += char_;
|
|
}
|
|
return str;
|
|
};
|
|
|
|
exports.each = function(obj, func, context) {
|
|
if(obj === null) {
|
|
return;
|
|
}
|
|
|
|
if(ArrayProto.each && obj.each == ArrayProto.each) {
|
|
obj.forEach(func, context);
|
|
}
|
|
else if(obj.length === +obj.length) {
|
|
for(var i=0, l=obj.length; i<l; i++) {
|
|
func.call(context, obj[i], i, obj);
|
|
}
|
|
}
|
|
};
|
|
|
|
exports.map = function(obj, func) {
|
|
var results = [];
|
|
if(obj === null) {
|
|
return results;
|
|
}
|
|
|
|
if(ArrayProto.map && obj.map === ArrayProto.map) {
|
|
return obj.map(func);
|
|
}
|
|
|
|
for(var i=0; i<obj.length; i++) {
|
|
results[results.length] = func(obj[i], i);
|
|
}
|
|
|
|
if(obj.length === +obj.length) {
|
|
results.length = obj.length;
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
exports.asyncParallel = function(funcs, done) {
|
|
var count = funcs.length,
|
|
result = new Array(count),
|
|
current = 0;
|
|
|
|
var makeNext = function(i) {
|
|
return function(res) {
|
|
result[i] = res;
|
|
current += 1;
|
|
|
|
if (current === count) {
|
|
done(result);
|
|
}
|
|
};
|
|
};
|
|
|
|
for (var i = 0; i < count; i++) {
|
|
funcs[i](makeNext(i));
|
|
}
|
|
};
|
|
|
|
exports.asyncIter = function(arr, iter, cb) {
|
|
var i = -1;
|
|
|
|
function next() {
|
|
i++;
|
|
|
|
if(i < arr.length) {
|
|
iter(arr[i], i, next, cb);
|
|
}
|
|
else {
|
|
cb();
|
|
}
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
exports.asyncFor = function(obj, iter, cb) {
|
|
var keys = exports.keys(obj);
|
|
var len = keys.length;
|
|
var i = -1;
|
|
|
|
function next() {
|
|
i++;
|
|
var k = keys[i];
|
|
|
|
if(i < len) {
|
|
iter(k, obj[k], i, len, next);
|
|
}
|
|
else {
|
|
cb();
|
|
}
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
if(!Array.prototype.indexOf) {
|
|
Array.prototype.indexOf = function(array, searchElement /*, fromIndex */) {
|
|
if (array === null) {
|
|
throw new TypeError();
|
|
}
|
|
var t = Object(array);
|
|
var len = t.length >>> 0;
|
|
if (len === 0) {
|
|
return -1;
|
|
}
|
|
var n = 0;
|
|
if (arguments.length > 2) {
|
|
n = Number(arguments[2]);
|
|
if (n != n) { // shortcut for verifying if it's NaN
|
|
n = 0;
|
|
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
|
|
n = (n > 0 || -1) * Math.floor(Math.abs(n));
|
|
}
|
|
}
|
|
if (n >= len) {
|
|
return -1;
|
|
}
|
|
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
|
|
for (; k < len; k++) {
|
|
if (k in t && t[k] === searchElement) {
|
|
return k;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
}
|
|
|
|
if(!Array.prototype.map) {
|
|
Array.prototype.map = function() {
|
|
throw new Error("map is unimplemented for this js engine");
|
|
};
|
|
}
|
|
|
|
exports.keys = function(obj) {
|
|
if(Object.prototype.keys) {
|
|
return obj.keys();
|
|
}
|
|
else {
|
|
var keys = [];
|
|
for(var k in obj) {
|
|
if(obj.hasOwnProperty(k)) {
|
|
keys.push(k);
|
|
}
|
|
}
|
|
return keys;
|
|
}
|
|
};
|
|
})();
|
|
(function() {
|
|
|
|
var lib = modules.lib;
|
|
var Object = modules.object;
|
|
|
|
// Frames keep track of scoping both at compile-time and run-time so
|
|
// we know how to access variables. Block tags can introduce special
|
|
// variables, for example.
|
|
var Frame = Object.extend({
|
|
init: function(parent) {
|
|
this.variables = {};
|
|
this.parent = parent;
|
|
},
|
|
|
|
set: function(name, val) {
|
|
// Allow variables with dots by automatically creating the
|
|
// nested structure
|
|
var parts = name.split('.');
|
|
var obj = this.variables;
|
|
|
|
for(var i=0; i<parts.length - 1; i++) {
|
|
var id = parts[i];
|
|
|
|
if(!obj[id]) {
|
|
obj[id] = {};
|
|
}
|
|
obj = obj[id];
|
|
}
|
|
|
|
obj[parts[parts.length - 1]] = val;
|
|
},
|
|
|
|
get: function(name) {
|
|
var val = this.variables[name];
|
|
if(val !== undefined && val !== null) {
|
|
return val;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
lookup: function(name) {
|
|
var p = this.parent;
|
|
var val = this.variables[name];
|
|
if(val !== undefined && val !== null) {
|
|
return val;
|
|
}
|
|
return p && p.lookup(name);
|
|
},
|
|
|
|
push: function() {
|
|
return new Frame(this);
|
|
},
|
|
|
|
pop: function() {
|
|
return this.parent;
|
|
}
|
|
});
|
|
|
|
function makeMacro(argNames, kwargNames, func) {
|
|
return function() {
|
|
var argCount = numArgs(arguments);
|
|
var args;
|
|
var kwargs = getKeywordArgs(arguments);
|
|
|
|
var i;
|
|
if(argCount > argNames.length) {
|
|
args = Array.prototype.slice.call(arguments, 0, argNames.length);
|
|
|
|
// Positional arguments that should be passed in as
|
|
// keyword arguments (essentially default values)
|
|
var vals = Array.prototype.slice.call(arguments, args.length, argCount);
|
|
for(i=0; i<vals.length; i++) {
|
|
if(i < kwargNames.length) {
|
|
kwargs[kwargNames[i]] = vals[i];
|
|
}
|
|
}
|
|
|
|
args.push(kwargs);
|
|
}
|
|
else if(argCount < argNames.length) {
|
|
args = Array.prototype.slice.call(arguments, 0, argCount);
|
|
|
|
for(i=argCount; i<argNames.length; i++) {
|
|
var arg = argNames[i];
|
|
|
|
// Keyword arguments that should be passed as
|
|
// positional arguments, i.e. the caller explicitly
|
|
// used the name of a positional arg
|
|
args.push(kwargs[arg]);
|
|
delete kwargs[arg];
|
|
}
|
|
|
|
args.push(kwargs);
|
|
}
|
|
else {
|
|
args = arguments;
|
|
}
|
|
|
|
return func.apply(this, args);
|
|
};
|
|
}
|
|
|
|
function makeKeywordArgs(obj) {
|
|
obj.__keywords = true;
|
|
return obj;
|
|
}
|
|
|
|
function getKeywordArgs(args) {
|
|
var len = args.length;
|
|
if(len) {
|
|
var lastArg = args[len - 1];
|
|
if(lastArg && lastArg.hasOwnProperty('__keywords')) {
|
|
return lastArg;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
function numArgs(args) {
|
|
var len = args.length;
|
|
if(len === 0) {
|
|
return 0;
|
|
}
|
|
|
|
var lastArg = args[len - 1];
|
|
if(lastArg && lastArg.hasOwnProperty('__keywords')) {
|
|
return len - 1;
|
|
}
|
|
else {
|
|
return len;
|
|
}
|
|
}
|
|
|
|
// A SafeString object indicates that the string should not be
|
|
// autoescaped. This happens magically because autoescaping only
|
|
// occurs on primitive string objects.
|
|
function SafeString(val) {
|
|
if(typeof val != 'string') {
|
|
return val;
|
|
}
|
|
|
|
this.toString = function() {
|
|
return val;
|
|
};
|
|
|
|
this.length = val.length;
|
|
|
|
var methods = [
|
|
'charAt', 'charCodeAt', 'concat', 'contains',
|
|
'endsWith', 'fromCharCode', 'indexOf', 'lastIndexOf',
|
|
'length', 'localeCompare', 'match', 'quote', 'replace',
|
|
'search', 'slice', 'split', 'startsWith', 'substr',
|
|
'substring', 'toLocaleLowerCase', 'toLocaleUpperCase',
|
|
'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight'
|
|
];
|
|
|
|
for(var i=0; i<methods.length; i++) {
|
|
this[methods[i]] = markSafe(val[methods[i]]);
|
|
}
|
|
}
|
|
|
|
function copySafeness(dest, target) {
|
|
if(dest instanceof SafeString) {
|
|
return new SafeString(target);
|
|
}
|
|
return target.toString();
|
|
}
|
|
|
|
function markSafe(val) {
|
|
var type = typeof val;
|
|
|
|
if(type === 'string') {
|
|
return new SafeString(val);
|
|
}
|
|
else if(type !== 'function') {
|
|
return val;
|
|
}
|
|
else {
|
|
return function() {
|
|
var ret = val.apply(this, arguments);
|
|
|
|
if(typeof ret === 'string') {
|
|
return new SafeString(ret);
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
}
|
|
}
|
|
|
|
function suppressValue(val, autoescape) {
|
|
val = (val !== undefined && val !== null) ? val : "";
|
|
|
|
if(autoescape && typeof val === "string") {
|
|
val = lib.escape(val);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
function memberLookup(obj, val) {
|
|
obj = obj || {};
|
|
|
|
if(typeof obj[val] === 'function') {
|
|
return function() {
|
|
return obj[val].apply(obj, arguments);
|
|
};
|
|
}
|
|
|
|
return obj[val];
|
|
}
|
|
|
|
function callWrap(obj, name, args) {
|
|
if(!obj) {
|
|
throw new Error('Unable to call `' + name + '`, which is undefined or falsey');
|
|
}
|
|
else if(typeof obj !== 'function') {
|
|
throw new Error('Unable to call `' + name + '`, which is not a function');
|
|
}
|
|
|
|
return obj.apply(this, args);
|
|
}
|
|
|
|
function contextOrFrameLookup(context, frame, name) {
|
|
var val = frame.lookup(name);
|
|
return (val !== undefined && val !== null) ?
|
|
val :
|
|
context.lookup(name);
|
|
}
|
|
|
|
function handleError(error, lineno, colno) {
|
|
if(error.lineno) {
|
|
return error;
|
|
}
|
|
else {
|
|
return new lib.TemplateError(error, lineno, colno);
|
|
}
|
|
}
|
|
|
|
function asyncEach(arr, dimen, iter, cb) {
|
|
if(lib.isArray(arr)) {
|
|
var len = arr.length;
|
|
|
|
lib.asyncIter(arr, function(item, i, next) {
|
|
switch(dimen) {
|
|
case 1: iter(item, i, len, next); break;
|
|
case 2: iter(item[0], item[1], i, len, next); break;
|
|
case 3: iter(item[0], item[1], item[2], i, len, next); break;
|
|
default:
|
|
item.push(i, next);
|
|
iter.apply(this, item);
|
|
}
|
|
}, cb);
|
|
}
|
|
else {
|
|
lib.asyncFor(arr, function(key, val, i, len, next) {
|
|
iter(key, val, i, len, next);
|
|
}, cb);
|
|
}
|
|
}
|
|
|
|
function asyncAll(arr, dimen, func, cb) {
|
|
var finished = 0;
|
|
var len;
|
|
var outputArr;
|
|
|
|
function done(i, output) {
|
|
finished++;
|
|
outputArr[i] = output;
|
|
|
|
if(finished == len) {
|
|
cb(null, outputArr.join(''));
|
|
}
|
|
}
|
|
|
|
if(lib.isArray(arr)) {
|
|
len = arr.length;
|
|
outputArr = new Array(len);
|
|
|
|
if(len === 0) {
|
|
cb(null, '');
|
|
}
|
|
else {
|
|
for(var i=0; i<arr.length; i++) {
|
|
var item = arr[i];
|
|
|
|
switch(dimen) {
|
|
case 1: func(item, i, len, done); break;
|
|
case 2: func(item[0], item[1], i, len, done); break;
|
|
case 3: func(item[0], item[1], item[2], i, len, done); break;
|
|
default:
|
|
item.push(i, done);
|
|
func.apply(this, item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var keys = lib.keys(arr);
|
|
len = keys.length;
|
|
outputArr = new Array(len);
|
|
|
|
if(len === 0) {
|
|
cb(null, '');
|
|
}
|
|
else {
|
|
for(var i=0; i<keys.length; i++) {
|
|
var k = keys[i];
|
|
func(k, arr[k], i, len, done);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
modules.runtime = {
|
|
Frame: Frame,
|
|
makeMacro: makeMacro,
|
|
makeKeywordArgs: makeKeywordArgs,
|
|
numArgs: numArgs,
|
|
suppressValue: suppressValue,
|
|
memberLookup: memberLookup,
|
|
contextOrFrameLookup: contextOrFrameLookup,
|
|
callWrap: callWrap,
|
|
handleError: handleError,
|
|
isArray: lib.isArray,
|
|
keys: lib.keys,
|
|
SafeString: SafeString,
|
|
copySafeness: copySafeness,
|
|
markSafe: markSafe,
|
|
asyncEach: asyncEach,
|
|
asyncAll: asyncAll
|
|
};
|
|
})();
|
|
(function() {
|
|
|
|
var lib = modules.lib;
|
|
var r = modules.runtime;
|
|
|
|
var filters = {
|
|
abs: function(n) {
|
|
return Math.abs(n);
|
|
},
|
|
|
|
batch: function(arr, linecount, fill_with) {
|
|
var res = [];
|
|
var tmp = [];
|
|
|
|
for(var i=0; i<arr.length; i++) {
|
|
if(i % linecount === 0 && tmp.length) {
|
|
res.push(tmp);
|
|
tmp = [];
|
|
}
|
|
|
|
tmp.push(arr[i]);
|
|
}
|
|
|
|
if(tmp.length) {
|
|
if(fill_with) {
|
|
for(i=tmp.length; i<linecount; i++) {
|
|
tmp.push(fill_with);
|
|
}
|
|
}
|
|
|
|
res.push(tmp);
|
|
}
|
|
|
|
return res;
|
|
},
|
|
|
|
capitalize: function(str) {
|
|
var ret = str.toLowerCase();
|
|
return r.copySafeness(str, ret.charAt(0).toUpperCase() + ret.slice(1));
|
|
},
|
|
|
|
center: function(str, width) {
|
|
width = width || 80;
|
|
|
|
if(str.length >= width) {
|
|
return str;
|
|
}
|
|
|
|
var spaces = width - str.length;
|
|
var pre = lib.repeat(" ", spaces/2 - spaces % 2);
|
|
var post = lib.repeat(" ", spaces/2);
|
|
return r.copySafeness(str, pre + str + post);
|
|
},
|
|
|
|
'default': function(val, def) {
|
|
return val ? val : def;
|
|
},
|
|
|
|
dictsort: function(val, case_sensitive, by) {
|
|
if (!lib.isObject(val)) {
|
|
throw new lib.TemplateError("dictsort filter: val must be an object");
|
|
}
|
|
|
|
var array = [];
|
|
for (var k in val) {
|
|
// deliberately include properties from the object's prototype
|
|
array.push([k,val[k]]);
|
|
}
|
|
|
|
var si;
|
|
if (by === undefined || by === "key") {
|
|
si = 0;
|
|
} else if (by === "value") {
|
|
si = 1;
|
|
} else {
|
|
throw new lib.TemplateError(
|
|
"dictsort filter: You can only sort by either key or value");
|
|
}
|
|
|
|
array.sort(function(t1, t2) {
|
|
var a = t1[si];
|
|
var b = t2[si];
|
|
|
|
if (!case_sensitive) {
|
|
if (lib.isString(a)) {
|
|
a = a.toUpperCase();
|
|
}
|
|
if (lib.isString(b)) {
|
|
b = b.toUpperCase();
|
|
}
|
|
}
|
|
|
|
return a > b ? 1 : (a == b ? 0 : -1);
|
|
});
|
|
|
|
return array;
|
|
},
|
|
|
|
escape: function(str) {
|
|
if(typeof str == 'string' ||
|
|
str instanceof r.SafeString) {
|
|
return lib.escape(str);
|
|
}
|
|
return str;
|
|
},
|
|
|
|
safe: r.markSafe,
|
|
|
|
first: function(arr) {
|
|
return arr[0];
|
|
},
|
|
|
|
groupby: function(arr, attr) {
|
|
return lib.groupBy(arr, attr);
|
|
},
|
|
|
|
indent: function(str, width, indentfirst) {
|
|
width = width || 4;
|
|
var res = '';
|
|
var lines = str.split('\n');
|
|
var sp = lib.repeat(' ', width);
|
|
|
|
for(var i=0; i<lines.length; i++) {
|
|
if(i === 0 && !indentfirst) {
|
|
res += lines[i] + '\n';
|
|
}
|
|
else {
|
|
res += sp + lines[i] + '\n';
|
|
}
|
|
}
|
|
|
|
return r.copySafeness(str, res);
|
|
},
|
|
|
|
join: function(arr, del, attr) {
|
|
del = del || '';
|
|
|
|
if(attr) {
|
|
arr = lib.map(arr, function(v) {
|
|
return v[attr];
|
|
});
|
|
}
|
|
|
|
return arr.join(del);
|
|
},
|
|
|
|
last: function(arr) {
|
|
return arr[arr.length-1];
|
|
},
|
|
|
|
length: function(arr) {
|
|
return arr.length;
|
|
},
|
|
|
|
list: function(val) {
|
|
if(lib.isString(val)) {
|
|
return val.split('');
|
|
}
|
|
else if(lib.isObject(val)) {
|
|
var keys = [];
|
|
|
|
if(Object.keys) {
|
|
keys = Object.keys(val);
|
|
}
|
|
else {
|
|
for(var k in val) {
|
|
keys.push(k);
|
|
}
|
|
}
|
|
|
|
return lib.map(keys, function(k) {
|
|
return { key: k,
|
|
value: val[k] };
|
|
});
|
|
}
|
|
else {
|
|
throw new lib.TemplateError("list filter: type not iterable");
|
|
}
|
|
},
|
|
|
|
lower: function(str) {
|
|
return str.toLowerCase();
|
|
},
|
|
|
|
random: function(arr) {
|
|
return arr[Math.floor(Math.random() * arr.length)];
|
|
},
|
|
|
|
replace: function(str, old, new_, maxCount) {
|
|
var res = str;
|
|
var last = res;
|
|
var count = 1;
|
|
res = res.replace(old, new_);
|
|
|
|
while(last != res) {
|
|
if(count >= maxCount) {
|
|
break;
|
|
}
|
|
|
|
last = res;
|
|
res = res.replace(old, new_);
|
|
count++;
|
|
}
|
|
|
|
return r.copySafeness(str, res);
|
|
},
|
|
|
|
reverse: function(val) {
|
|
var arr;
|
|
if(lib.isString(val)) {
|
|
arr = filters.list(val);
|
|
}
|
|
else {
|
|
// Copy it
|
|
arr = lib.map(val, function(v) { return v; });
|
|
}
|
|
|
|
arr.reverse();
|
|
|
|
if(lib.isString(val)) {
|
|
return r.copySafeness(val, arr.join(''));
|
|
}
|
|
return arr;
|
|
},
|
|
|
|
round: function(val, precision, method) {
|
|
precision = precision || 0;
|
|
var factor = Math.pow(10, precision);
|
|
var rounder;
|
|
|
|
if(method == 'ceil') {
|
|
rounder = Math.ceil;
|
|
}
|
|
else if(method == 'floor') {
|
|
rounder = Math.floor;
|
|
}
|
|
else {
|
|
rounder = Math.round;
|
|
}
|
|
|
|
return rounder(val * factor) / factor;
|
|
},
|
|
|
|
slice: function(arr, slices, fillWith) {
|
|
var sliceLength = Math.floor(arr.length / slices);
|
|
var extra = arr.length % slices;
|
|
var offset = 0;
|
|
var res = [];
|
|
|
|
for(var i=0; i<slices; i++) {
|
|
var start = offset + i * sliceLength;
|
|
if(i < extra) {
|
|
offset++;
|
|
}
|
|
var end = offset + (i + 1) * sliceLength;
|
|
|
|
var slice = arr.slice(start, end);
|
|
if(fillWith && i >= extra) {
|
|
slice.push(fillWith);
|
|
}
|
|
res.push(slice);
|
|
}
|
|
|
|
return res;
|
|
},
|
|
|
|
sort: function(arr, reverse, caseSens, attr) {
|
|
// Copy it
|
|
arr = lib.map(arr, function(v) { return v; });
|
|
|
|
arr.sort(function(a, b) {
|
|
var x, y;
|
|
|
|
if(attr) {
|
|
x = a[attr];
|
|
y = b[attr];
|
|
}
|
|
else {
|
|
x = a;
|
|
y = b;
|
|
}
|
|
|
|
if(!caseSens && lib.isString(x) && lib.isString(y)) {
|
|
x = x.toLowerCase();
|
|
y = y.toLowerCase();
|
|
}
|
|
|
|
if(x < y) {
|
|
return reverse ? 1 : -1;
|
|
}
|
|
else if(x > y) {
|
|
return reverse ? -1: 1;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
return arr;
|
|
},
|
|
|
|
string: function(obj) {
|
|
return r.copySafeness(obj, obj);
|
|
},
|
|
|
|
title: function(str) {
|
|
var words = str.split(' ');
|
|
for(var i = 0; i < words.length; i++) {
|
|
words[i] = filters.capitalize(words[i]);
|
|
}
|
|
return r.copySafeness(str, words.join(' '));
|
|
},
|
|
|
|
trim: function(str) {
|
|
return r.copySafeness(str, str.replace(/^\s*|\s*$/g, ''));
|
|
},
|
|
|
|
truncate: function(input, length, killwords, end) {
|
|
var orig = input;
|
|
length = length || 255;
|
|
|
|
if (input.length <= length)
|
|
return input;
|
|
|
|
if (killwords) {
|
|
input = input.substring(0, length);
|
|
} else {
|
|
var idx = input.lastIndexOf(' ', length);
|
|
if(idx === -1) {
|
|
idx = length;
|
|
}
|
|
|
|
input = input.substring(0, idx);
|
|
}
|
|
|
|
input += (end !== undefined && end !== null) ? end : '...';
|
|
return r.copySafeness(orig, input);
|
|
},
|
|
|
|
upper: function(str) {
|
|
return str.toUpperCase();
|
|
},
|
|
|
|
urlize: function(str, length, nofollow) {
|
|
if (isNaN(length)) length = Infinity;
|
|
|
|
var noFollowAttr = (nofollow === true ? ' rel="nofollow"' : '');
|
|
|
|
// For the jinja regexp, see
|
|
// https://github.com/mitsuhiko/jinja2/blob/f15b814dcba6aa12bc74d1f7d0c881d55f7126be/jinja2/utils.py#L20-L23
|
|
var puncRE = /^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/;
|
|
// from http://blog.gerv.net/2011/05/html5_email_address_regexp/
|
|
var emailRE = /^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i;
|
|
var httpHttpsRE = /^https?:\/\/.*$/;
|
|
var wwwRE = /^www\./;
|
|
var tldRE = /\.(?:org|net|com)(?:\:|\/|$)/;
|
|
|
|
var words = str.split(/\s+/).filter(function(word) {
|
|
// If the word has no length, bail. This can happen for str with
|
|
// trailing whitespace.
|
|
return word && word.length;
|
|
}).map(function(word) {
|
|
var matches = word.match(puncRE);
|
|
|
|
var possibleUrl = matches && matches[1] || word;
|
|
|
|
// url that starts with http or https
|
|
if (httpHttpsRE.test(possibleUrl))
|
|
return '<a href="' + possibleUrl + '"' + noFollowAttr + '>' + possibleUrl.substr(0, length) + '</a>';
|
|
|
|
// url that starts with www.
|
|
if (wwwRE.test(possibleUrl))
|
|
return '<a href="http://' + possibleUrl + '"' + noFollowAttr + '>' + possibleUrl.substr(0, length) + '</a>';
|
|
|
|
// an email address of the form username@domain.tld
|
|
if (emailRE.test(possibleUrl))
|
|
return '<a href="mailto:' + possibleUrl + '">' + possibleUrl + '</a>';
|
|
|
|
// url that ends in .com, .org or .net that is not an email address
|
|
if (tldRE.test(possibleUrl))
|
|
return '<a href="http://' + possibleUrl + '"' + noFollowAttr + '>' + possibleUrl.substr(0, length) + '</a>';
|
|
|
|
return possibleUrl;
|
|
|
|
});
|
|
|
|
return words.join(' ');
|
|
},
|
|
|
|
wordcount: function(str) {
|
|
return str.match(/\w+/g).length;
|
|
},
|
|
|
|
'float': function(val, def) {
|
|
var res = parseFloat(val);
|
|
return isNaN(res) ? def : res;
|
|
},
|
|
|
|
'int': function(val, def) {
|
|
var res = parseInt(val, 10);
|
|
return isNaN(res) ? def : res;
|
|
}
|
|
};
|
|
|
|
// Aliases
|
|
filters.d = filters['default'];
|
|
filters.e = filters.escape;
|
|
|
|
modules.filters = filters;
|
|
})();
|
|
(function() {
|
|
|
|
function cycler(items) {
|
|
var index = -1;
|
|
var current = null;
|
|
|
|
return {
|
|
reset: function() {
|
|
index = -1;
|
|
current = null;
|
|
},
|
|
|
|
next: function() {
|
|
index++;
|
|
if(index >= items.length) {
|
|
index = 0;
|
|
}
|
|
|
|
current = items[index];
|
|
return current;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
function joiner(sep) {
|
|
sep = sep || ',';
|
|
var first = true;
|
|
|
|
return function() {
|
|
var val = first ? '' : sep;
|
|
first = false;
|
|
return val;
|
|
};
|
|
}
|
|
|
|
var globals = {
|
|
range: function(start, stop, step) {
|
|
if(!stop) {
|
|
stop = start;
|
|
start = 0;
|
|
step = 1;
|
|
}
|
|
else if(!step) {
|
|
step = 1;
|
|
}
|
|
|
|
var arr = [];
|
|
for(var i=start; i<stop; i+=step) {
|
|
arr.push(i);
|
|
}
|
|
return arr;
|
|
},
|
|
|
|
cycler: function() {
|
|
return cycler(Array.prototype.slice.call(arguments));
|
|
},
|
|
|
|
joiner: function(sep) {
|
|
return joiner(sep);
|
|
}
|
|
};
|
|
|
|
modules.globals = globals;
|
|
})();
|
|
(function() {
|
|
var lib = modules.lib;
|
|
var Obj = modules.object;
|
|
var builtin_filters = modules.filters;
|
|
var runtime = modules.runtime;
|
|
var globals = modules.globals;
|
|
var Frame = runtime.Frame;
|
|
|
|
var Environment = Obj.extend({
|
|
init: function(loaders, opts) {
|
|
// The dev flag determines the trace that'll be shown on errors.
|
|
// If set to true, returns the full trace from the error point,
|
|
// otherwise will return trace starting from Template.render
|
|
// (the full trace from within nunjucks may confuse developers using
|
|
// the library)
|
|
// defaults to false
|
|
opts = opts || {};
|
|
this.dev = !!opts.dev;
|
|
|
|
// The autoescape flag sets global autoescaping. If true,
|
|
// every string variable will be escaped by default.
|
|
// If false, strings can be manually escaped using the `escape` filter.
|
|
// defaults to false
|
|
this.autoesc = !!opts.autoescape;
|
|
|
|
this.filters = {};
|
|
this.asyncFilters = [];
|
|
this.extensions = {};
|
|
this.extensionsList = [];
|
|
|
|
for(var name in builtin_filters) {
|
|
this.addFilter(name, builtin_filters[name]);
|
|
}
|
|
},
|
|
|
|
addExtension: function(name, extension) {
|
|
extension._name = name;
|
|
this.extensions[name] = extension;
|
|
this.extensionsList.push(extension);
|
|
},
|
|
|
|
getExtension: function(name) {
|
|
return this.extensions[name];
|
|
},
|
|
|
|
addFilter: function(name, func, async) {
|
|
var wrapped = func;
|
|
|
|
if(async) {
|
|
this.asyncFilters.push(name);
|
|
}
|
|
this.filters[name] = wrapped;
|
|
},
|
|
|
|
getFilter: function(name) {
|
|
if(!this.filters[name]) {
|
|
throw new Error('filter not found: ' + name);
|
|
}
|
|
return this.filters[name];
|
|
},
|
|
|
|
getTemplate: function(name, cb) {
|
|
if(name && name.raw) {
|
|
// this fixes autoescape for templates referenced in symbols
|
|
name = name.raw;
|
|
}
|
|
|
|
if(typeof name !== 'string') {
|
|
throw new Error('template names must be a string: ' + name);
|
|
}
|
|
|
|
var tmpl = this.cache[name];
|
|
var env = this;
|
|
tmpl.render = function(ctx, frame, cb) {
|
|
return this.root(env, new Context(ctx), frame, runtime, cb);
|
|
};
|
|
tmpl.getExported = function(cb) {
|
|
var ctx = new Context({});
|
|
this.root(env, ctx, new Frame(), runtime, function() {
|
|
cb(null, ctx.getExported());
|
|
});
|
|
};
|
|
|
|
if(tmpl) {
|
|
if(cb) {
|
|
cb.call(this, null, tmpl);
|
|
}
|
|
else {
|
|
return tmpl;
|
|
}
|
|
} else {
|
|
throw new Error('Template not available: "' + name + '"');
|
|
}
|
|
},
|
|
|
|
render: function(name, ctx, cb) {
|
|
if(lib.isFunction(ctx)) {
|
|
cb = ctx;
|
|
ctx = null;
|
|
}
|
|
|
|
// We support a synchronous API to make it easier to migrate
|
|
// existing code to async. This works because if you don't do
|
|
// anything async work, the whole thing is actually run
|
|
// synchronously.
|
|
var syncResult = null;
|
|
|
|
this.getTemplate(name, function(err, tmpl) {
|
|
if(err && cb) {
|
|
cb(err);
|
|
}
|
|
else if(err) {
|
|
throw err;
|
|
}
|
|
else {
|
|
tmpl.root(this, new Context(ctx || {}), new Frame(), runtime, cb || function(err, res) {
|
|
if(err) { throw err; }
|
|
syncResult = res;
|
|
});
|
|
}
|
|
});
|
|
|
|
return syncResult;
|
|
}
|
|
});
|
|
|
|
var Context = Obj.extend({
|
|
init: function(ctx, blocks) {
|
|
this.ctx = ctx;
|
|
this.blocks = {};
|
|
this.exported = [];
|
|
|
|
for(var name in blocks) {
|
|
this.addBlock(name, blocks[name]);
|
|
}
|
|
},
|
|
|
|
lookup: function(name) {
|
|
// This is one of the most called functions, so optimize for
|
|
// the typical case where the name isn't in the globals
|
|
if(name in globals && !(name in this.ctx)) {
|
|
return globals[name];
|
|
}
|
|
else {
|
|
return this.ctx[name];
|
|
}
|
|
},
|
|
|
|
setVariable: function(name, val) {
|
|
this.ctx[name] = val;
|
|
},
|
|
|
|
getVariables: function() {
|
|
return this.ctx;
|
|
},
|
|
|
|
addBlock: function(name, block) {
|
|
this.blocks[name] = this.blocks[name] || [];
|
|
this.blocks[name].push(block);
|
|
},
|
|
|
|
getBlock: function(name) {
|
|
if(!this.blocks[name]) {
|
|
throw new Error('unknown block "' + name + '"');
|
|
}
|
|
|
|
return this.blocks[name][0];
|
|
},
|
|
|
|
getSuper: function(env, name, block, frame, runtime, cb) {
|
|
var idx = (this.blocks[name] || []).indexOf(block);
|
|
var blk = this.blocks[name][idx + 1];
|
|
var context = this;
|
|
|
|
if(idx == -1 || !blk) {
|
|
throw new Error('no super block available for "' + name + '"');
|
|
}
|
|
|
|
blk(env, context, frame, runtime, cb);
|
|
},
|
|
|
|
addExport: function(name) {
|
|
this.exported.push(name);
|
|
},
|
|
|
|
getExported: function() {
|
|
var exported = {};
|
|
for(var i=0; i<this.exported.length; i++) {
|
|
var name = this.exported[i];
|
|
exported[name] = this.ctx[name];
|
|
}
|
|
return exported;
|
|
}
|
|
});
|
|
|
|
modules.environment = {Environment: Environment};
|
|
})();
|
|
var nunjucks;
|
|
|
|
var lib = modules.lib;
|
|
var env = modules.environment;
|
|
var runtime = modules.runtime;
|
|
|
|
nunjucks = {};
|
|
nunjucks.Environment = env.Environment;
|
|
|
|
nunjucks.runtime = runtime;
|
|
|
|
// A single instance of an environment, since this is so commonly used
|
|
|
|
var e;
|
|
nunjucks.configure = function(templatesPath, opts) {
|
|
opts = opts || {};
|
|
if(lib.isObject(templatesPath)) {
|
|
opts = templatesPath;
|
|
templatesPath = null;
|
|
}
|
|
|
|
var noWatch = 'watch' in opts ? !opts.watch : false;
|
|
e = new env.Environment(null, opts);
|
|
|
|
return e;
|
|
};
|
|
|
|
nunjucks.render = function(name, ctx, cb) {
|
|
if(!e) {
|
|
nunjucks.configure();
|
|
}
|
|
|
|
return e.render(name, ctx, cb);
|
|
};
|
|
|
|
nunjucks.require = function(name) { return modules[name]; };
|
|
define('nunjucks', [], function() { return nunjucks; });
|
|
|
|
})();
|