UNPKG

nunjucks

Version:

A powerful templating engine with inheritance, asynchronous control, and more (jinja2 inspired)

546 lines (528 loc) 14.9 kB
'use strict'; var lib = require('./lib'); var r = require('./runtime'); var _exports = module.exports = {}; function normalize(value, defaultValue) { if (value === null || value === undefined || value === false) { return defaultValue; } return value; } _exports.abs = Math.abs; function isNaN(num) { return num !== num; // eslint-disable-line no-self-compare } function batch(arr, linecount, fillWith) { var i; var res = []; var tmp = []; for (i = 0; i < arr.length; i++) { if (i % linecount === 0 && tmp.length) { res.push(tmp); tmp = []; } tmp.push(arr[i]); } if (tmp.length) { if (fillWith) { for (i = tmp.length; i < linecount; i++) { tmp.push(fillWith); } } res.push(tmp); } return res; } _exports.batch = batch; function capitalize(str) { str = normalize(str, ''); var ret = str.toLowerCase(); return r.copySafeness(str, ret.charAt(0).toUpperCase() + ret.slice(1)); } _exports.capitalize = capitalize; function center(str, width) { str = normalize(str, ''); 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); } _exports.center = center; function default_(val, def, bool) { if (bool) { return val || def; } else { return val !== undefined ? val : def; } } // TODO: it is confusing to export something called 'default' _exports['default'] = default_; // eslint-disable-line dot-notation function dictsort(val, caseSensitive, by) { if (!lib.isObject(val)) { throw new lib.TemplateError('dictsort filter: val must be an object'); } var array = []; // deliberately include properties from the object's prototype for (var k in val) { // eslint-disable-line guard-for-in, no-restricted-syntax 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 (!caseSensitive) { if (lib.isString(a)) { a = a.toUpperCase(); } if (lib.isString(b)) { b = b.toUpperCase(); } } return a > b ? 1 : a === b ? 0 : -1; // eslint-disable-line no-nested-ternary }); return array; } _exports.dictsort = dictsort; function dump(obj, spaces) { return JSON.stringify(obj, null, spaces); } _exports.dump = dump; function escape(str) { if (str instanceof r.SafeString) { return str; } str = str === null || str === undefined ? '' : str; return r.markSafe(lib.escape(str.toString())); } _exports.escape = escape; function safe(str) { if (str instanceof r.SafeString) { return str; } str = str === null || str === undefined ? '' : str; return r.markSafe(str.toString()); } _exports.safe = safe; function first(arr) { return arr[0]; } _exports.first = first; function forceescape(str) { str = str === null || str === undefined ? '' : str; return r.markSafe(lib.escape(str.toString())); } _exports.forceescape = forceescape; function groupby(arr, attr) { return lib.groupBy(arr, attr, this.env.opts.throwOnUndefined); } _exports.groupby = groupby; function indent(str, width, indentfirst) { str = normalize(str, ''); if (str === '') { return ''; } width = width || 4; // let res = ''; var lines = str.split('\n'); var sp = lib.repeat(' ', width); var res = lines.map(function (l, i) { return i === 0 && !indentfirst ? l : "" + sp + l; }).join('\n'); return r.copySafeness(str, res); } _exports.indent = indent; function join(arr, del, attr) { del = del || ''; if (attr) { arr = lib.map(arr, function (v) { return v[attr]; }); } return arr.join(del); } _exports.join = join; function last(arr) { return arr[arr.length - 1]; } _exports.last = last; function lengthFilter(val) { var value = normalize(val, ''); if (value !== undefined) { if (typeof Map === 'function' && value instanceof Map || typeof Set === 'function' && value instanceof Set) { // ECMAScript 2015 Maps and Sets return value.size; } if (lib.isObject(value) && !(value instanceof r.SafeString)) { // Objects (besides SafeStrings), non-primative Arrays return lib.keys(value).length; } return value.length; } return 0; } _exports.length = lengthFilter; function list(val) { if (lib.isString(val)) { return val.split(''); } else if (lib.isObject(val)) { return lib._entries(val || {}).map(function (_ref) { var key = _ref[0], value = _ref[1]; return { key: key, value: value }; }); } else if (lib.isArray(val)) { return val; } else { throw new lib.TemplateError('list filter: type not iterable'); } } _exports.list = list; function lower(str) { str = normalize(str, ''); return str.toLowerCase(); } _exports.lower = lower; function nl2br(str) { if (str === null || str === undefined) { return ''; } return r.copySafeness(str, str.replace(/\r\n|\n/g, '<br />\n')); } _exports.nl2br = nl2br; function random(arr) { return arr[Math.floor(Math.random() * arr.length)]; } _exports.random = random; /** * Construct select or reject filter * * @param {boolean} expectedTestResult * @returns {function(array, string, *): array} */ function getSelectOrReject(expectedTestResult) { function filter(arr, testName, secondArg) { if (testName === void 0) { testName = 'truthy'; } var context = this; var test = context.env.getTest(testName); return lib.toArray(arr).filter(function examineTestResult(item) { return test.call(context, item, secondArg) === expectedTestResult; }); } return filter; } _exports.reject = getSelectOrReject(false); function rejectattr(arr, attr) { return arr.filter(function (item) { return !item[attr]; }); } _exports.rejectattr = rejectattr; _exports.select = getSelectOrReject(true); function selectattr(arr, attr) { return arr.filter(function (item) { return !!item[attr]; }); } _exports.selectattr = selectattr; function replace(str, old, new_, maxCount) { var originalStr = str; if (old instanceof RegExp) { return str.replace(old, new_); } if (typeof maxCount === 'undefined') { maxCount = -1; } var res = ''; // Output // Cast Numbers in the search term to string if (typeof old === 'number') { old = '' + old; } else if (typeof old !== 'string') { // If it is something other than number or string, // return the original string return str; } // Cast numbers in the replacement to string if (typeof str === 'number') { str = '' + str; } // If by now, we don't have a string, throw it back if (typeof str !== 'string' && !(str instanceof r.SafeString)) { return str; } // ShortCircuits if (old === '') { // Mimic the python behaviour: empty string is replaced // by replacement e.g. "abc"|replace("", ".") -> .a.b.c. res = new_ + str.split('').join(new_) + new_; return r.copySafeness(str, res); } var nextIndex = str.indexOf(old); // if # of replacements to perform is 0, or the string to does // not contain the old value, return the string if (maxCount === 0 || nextIndex === -1) { return str; } var pos = 0; var count = 0; // # of replacements made while (nextIndex > -1 && (maxCount === -1 || count < maxCount)) { // Grab the next chunk of src string and add it with the // replacement, to the result res += str.substring(pos, nextIndex) + new_; // Increment our pointer in the src string pos = nextIndex + old.length; count++; // See if there are any more replacements to be made nextIndex = str.indexOf(old, pos); } // We've either reached the end, or done the max # of // replacements, tack on any remaining string if (pos < str.length) { res += str.substring(pos); } return r.copySafeness(originalStr, res); } _exports.replace = replace; function reverse(val) { var arr; if (lib.isString(val)) { arr = 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; } _exports.reverse = reverse; function round(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; } _exports.round = round; function slice(arr, slices, fillWith) { var sliceLength = Math.floor(arr.length / slices); var extra = arr.length % slices; var res = []; var offset = 0; for (var i = 0; i < slices; i++) { var start = offset + i * sliceLength; if (i < extra) { offset++; } var end = offset + (i + 1) * sliceLength; var currSlice = arr.slice(start, end); if (fillWith && i >= extra) { currSlice.push(fillWith); } res.push(currSlice); } return res; } _exports.slice = slice; function sum(arr, attr, start) { if (start === void 0) { start = 0; } if (attr) { arr = lib.map(arr, function (v) { return v[attr]; }); } return start + arr.reduce(function (a, b) { return a + b; }, 0); } _exports.sum = sum; _exports.sort = r.makeMacro(['value', 'reverse', 'case_sensitive', 'attribute'], [], function sortFilter(arr, reversed, caseSens, attr) { var _this = this; // Copy it var array = lib.map(arr, function (v) { return v; }); var getAttribute = lib.getAttrGetter(attr); array.sort(function (a, b) { var x = attr ? getAttribute(a) : a; var y = attr ? getAttribute(b) : b; if (_this.env.opts.throwOnUndefined && attr && (x === undefined || y === undefined)) { throw new TypeError("sort: attribute \"" + attr + "\" resolved to undefined"); } if (!caseSens && lib.isString(x) && lib.isString(y)) { x = x.toLowerCase(); y = y.toLowerCase(); } if (x < y) { return reversed ? 1 : -1; } else if (x > y) { return reversed ? -1 : 1; } else { return 0; } }); return array; }); function string(obj) { return r.copySafeness(obj, obj); } _exports.string = string; function striptags(input, preserveLinebreaks) { input = normalize(input, ''); var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>|<!--[\s\S]*?-->/gi; var trimmedInput = trim(input.replace(tags, '')); var res = ''; if (preserveLinebreaks) { res = trimmedInput.replace(/^ +| +$/gm, '') // remove leading and trailing spaces .replace(/ +/g, ' ') // squash adjacent spaces .replace(/(\r\n)/g, '\n') // normalize linebreaks (CRLF -> LF) .replace(/\n\n\n+/g, '\n\n'); // squash abnormal adjacent linebreaks } else { res = trimmedInput.replace(/\s+/gi, ' '); } return r.copySafeness(input, res); } _exports.striptags = striptags; function title(str) { str = normalize(str, ''); var words = str.split(' ').map(function (word) { return capitalize(word); }); return r.copySafeness(str, words.join(' ')); } _exports.title = title; function trim(str) { return r.copySafeness(str, str.replace(/^\s*|\s*$/g, '')); } _exports.trim = trim; function truncate(input, length, killwords, end) { var orig = input; input = normalize(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); } _exports.truncate = truncate; function upper(str) { str = normalize(str, ''); return str.toUpperCase(); } _exports.upper = upper; function urlencode(obj) { var enc = encodeURIComponent; if (lib.isString(obj)) { return enc(obj); } else { var keyvals = lib.isArray(obj) ? obj : lib._entries(obj); return keyvals.map(function (_ref2) { var k = _ref2[0], v = _ref2[1]; return enc(k) + "=" + enc(v); }).join('&'); } } _exports.urlencode = urlencode; // For the jinja regexp, see // https://github.com/mitsuhiko/jinja2/blob/f15b814dcba6aa12bc74d1f7d0c881d55f7126be/jinja2/utils.py#L20-L23 var puncRe = /^(?:\(|<|&lt;)?(.*?)(?:\.|,|\)|\n|&gt;)?$/; // 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)(?:\:|\/|$)/; function urlize(str, length, nofollow) { if (isNaN(length)) { length = Infinity; } var noFollowAttr = nofollow === true ? ' rel="nofollow"' : ''; 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; var shortUrl = possibleUrl.substr(0, length); // url that starts with http or https if (httpHttpsRe.test(possibleUrl)) { return "<a href=\"" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</a>"; } // url that starts with www. if (wwwRe.test(possibleUrl)) { return "<a href=\"http://" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</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 + ">" + shortUrl + "</a>"; } return word; }); return words.join(''); } _exports.urlize = urlize; function wordcount(str) { str = normalize(str, ''); var words = str ? str.match(/\w+/g) : null; return words ? words.length : null; } _exports.wordcount = wordcount; function float(val, def) { var res = parseFloat(val); return isNaN(res) ? def : res; } _exports.float = float; var intFilter = r.makeMacro(['value', 'default', 'base'], [], function doInt(value, defaultValue, base) { if (base === void 0) { base = 10; } var res = parseInt(value, base); return isNaN(res) ? defaultValue : res; }); _exports.int = intFilter; // Aliases _exports.d = _exports.default; _exports.e = _exports.escape;