1'use strict'; 2 3var lib = require('./lib'); 4var r = require('./runtime'); 5var _exports = module.exports = {}; 6function normalize(value, defaultValue) { 7 if (value === null || value === undefined || value === false) { 8 return defaultValue; 9 } 10 return value; 11} 12_exports.abs = Math.abs; 13function isNaN(num) { 14 return num !== num; // eslint-disable-line no-self-compare 15} 16 17function batch(arr, linecount, fillWith) { 18 var i; 19 var res = []; 20 var tmp = []; 21 for (i = 0; i < arr.length; i++) { 22 if (i % linecount === 0 && tmp.length) { 23 res.push(tmp); 24 tmp = []; 25 } 26 tmp.push(arr[i]); 27 } 28 if (tmp.length) { 29 if (fillWith) { 30 for (i = tmp.length; i < linecount; i++) { 31 tmp.push(fillWith); 32 } 33 } 34 res.push(tmp); 35 } 36 return res; 37} 38_exports.batch = batch; 39function capitalize(str) { 40 str = normalize(str, ''); 41 var ret = str.toLowerCase(); 42 return r.copySafeness(str, ret.charAt(0).toUpperCase() + ret.slice(1)); 43} 44_exports.capitalize = capitalize; 45function center(str, width) { 46 str = normalize(str, ''); 47 width = width || 80; 48 if (str.length >= width) { 49 return str; 50 } 51 var spaces = width - str.length; 52 var pre = lib.repeat(' ', spaces / 2 - spaces % 2); 53 var post = lib.repeat(' ', spaces / 2); 54 return r.copySafeness(str, pre + str + post); 55} 56_exports.center = center; 57function default_(val, def, bool) { 58 if (bool) { 59 return val || def; 60 } else { 61 return val !== undefined ? val : def; 62 } 63} 64 65// TODO: it is confusing to export something called 'default' 66_exports['default'] = default_; // eslint-disable-line dot-notation 67 68function dictsort(val, caseSensitive, by) { 69 if (!lib.isObject(val)) { 70 throw new lib.TemplateError('dictsort filter: val must be an object'); 71 } 72 var array = []; 73 // deliberately include properties from the object's prototype 74 for (var k in val) { 75 // eslint-disable-line guard-for-in, no-restricted-syntax 76 array.push([k, val[k]]); 77 } 78 var si; 79 if (by === undefined || by === 'key') { 80 si = 0; 81 } else if (by === 'value') { 82 si = 1; 83 } else { 84 throw new lib.TemplateError('dictsort filter: You can only sort by either key or value'); 85 } 86 array.sort(function (t1, t2) { 87 var a = t1[si]; 88 var b = t2[si]; 89 if (!caseSensitive) { 90 if (lib.isString(a)) { 91 a = a.toUpperCase(); 92 } 93 if (lib.isString(b)) { 94 b = b.toUpperCase(); 95 } 96 } 97 return a > b ? 1 : a === b ? 0 : -1; // eslint-disable-line no-nested-ternary 98 }); 99 100 return array; 101} 102_exports.dictsort = dictsort; 103function dump(obj, spaces) { 104 return JSON.stringify(obj, null, spaces); 105} 106_exports.dump = dump; 107function escape(str) { 108 if (str instanceof r.SafeString) { 109 return str; 110 } 111 str = str === null || str === undefined ? '' : str; 112 return r.markSafe(lib.escape(str.toString())); 113} 114_exports.escape = escape; 115function safe(str) { 116 if (str instanceof r.SafeString) { 117 return str; 118 } 119 str = str === null || str === undefined ? '' : str; 120 return r.markSafe(str.toString()); 121} 122_exports.safe = safe; 123function first(arr) { 124 return arr[0]; 125} 126_exports.first = first; 127function forceescape(str) { 128 str = str === null || str === undefined ? '' : str; 129 return r.markSafe(lib.escape(str.toString())); 130} 131_exports.forceescape = forceescape; 132function groupby(arr, attr) { 133 return lib.groupBy(arr, attr, this.env.opts.throwOnUndefined); 134} 135_exports.groupby = groupby; 136function indent(str, width, indentfirst) { 137 str = normalize(str, ''); 138 if (str === '') { 139 return ''; 140 } 141 width = width || 4; 142 // let res = ''; 143 var lines = str.split('\n'); 144 var sp = lib.repeat(' ', width); 145 var res = lines.map(function (l, i) { 146 return i === 0 && !indentfirst ? l : "" + sp + l; 147 }).join('\n'); 148 return r.copySafeness(str, res); 149} 150_exports.indent = indent; 151function join(arr, del, attr) { 152 del = del || ''; 153 if (attr) { 154 arr = lib.map(arr, function (v) { 155 return v[attr]; 156 }); 157 } 158 return arr.join(del); 159} 160_exports.join = join; 161function last(arr) { 162 return arr[arr.length - 1]; 163} 164_exports.last = last; 165function lengthFilter(val) { 166 var value = normalize(val, ''); 167 if (value !== undefined) { 168 if (typeof Map === 'function' && value instanceof Map || typeof Set === 'function' && value instanceof Set) { 169 // ECMAScript 2015 Maps and Sets 170 return value.size; 171 } 172 if (lib.isObject(value) && !(value instanceof r.SafeString)) { 173 // Objects (besides SafeStrings), non-primative Arrays 174 return lib.keys(value).length; 175 } 176 return value.length; 177 } 178 return 0; 179} 180_exports.length = lengthFilter; 181function list(val) { 182 if (lib.isString(val)) { 183 return val.split(''); 184 } else if (lib.isObject(val)) { 185 return lib._entries(val || {}).map(function (_ref) { 186 var key = _ref[0], 187 value = _ref[1]; 188 return { 189 key: key, 190 value: value 191 }; 192 }); 193 } else if (lib.isArray(val)) { 194 return val; 195 } else { 196 throw new lib.TemplateError('list filter: type not iterable'); 197 } 198} 199_exports.list = list; 200function lower(str) { 201 str = normalize(str, ''); 202 return str.toLowerCase(); 203} 204_exports.lower = lower; 205function nl2br(str) { 206 if (str === null || str === undefined) { 207 return ''; 208 } 209 return r.copySafeness(str, str.replace(/\r\n|\n/g, '<br />\n')); 210} 211_exports.nl2br = nl2br; 212function random(arr) { 213 return arr[Math.floor(Math.random() * arr.length)]; 214} 215_exports.random = random; 216 217/** 218 * Construct select or reject filter 219 * 220 * @param {boolean} expectedTestResult 221 * @returns {function(array, string, *): array} 222 */ 223function getSelectOrReject(expectedTestResult) { 224 function filter(arr, testName, secondArg) { 225 if (testName === void 0) { 226 testName = 'truthy'; 227 } 228 var context = this; 229 var test = context.env.getTest(testName); 230 return lib.toArray(arr).filter(function examineTestResult(item) { 231 return test.call(context, item, secondArg) === expectedTestResult; 232 }); 233 } 234 return filter; 235} 236_exports.reject = getSelectOrReject(false); 237function rejectattr(arr, attr) { 238 return arr.filter(function (item) { 239 return !item[attr]; 240 }); 241} 242_exports.rejectattr = rejectattr; 243_exports.select = getSelectOrReject(true); 244function selectattr(arr, attr) { 245 return arr.filter(function (item) { 246 return !!item[attr]; 247 }); 248} 249_exports.selectattr = selectattr; 250function replace(str, old, new_, maxCount) { 251 var originalStr = str; 252 if (old instanceof RegExp) { 253 return str.replace(old, new_); 254 } 255 if (typeof maxCount === 'undefined') { 256 maxCount = -1; 257 } 258 var res = ''; // Output 259 260 // Cast Numbers in the search term to string 261 if (typeof old === 'number') { 262 old = '' + old; 263 } else if (typeof old !== 'string') { 264 // If it is something other than number or string, 265 // return the original string 266 return str; 267 } 268 269 // Cast numbers in the replacement to string 270 if (typeof str === 'number') { 271 str = '' + str; 272 } 273 274 // If by now, we don't have a string, throw it back 275 if (typeof str !== 'string' && !(str instanceof r.SafeString)) { 276 return str; 277 } 278 279 // ShortCircuits 280 if (old === '') { 281 // Mimic the python behaviour: empty string is replaced 282 // by replacement e.g. "abc"|replace("", ".") -> .a.b.c. 283 res = new_ + str.split('').join(new_) + new_; 284 return r.copySafeness(str, res); 285 } 286 var nextIndex = str.indexOf(old); 287 // if # of replacements to perform is 0, or the string to does 288 // not contain the old value, return the string 289 if (maxCount === 0 || nextIndex === -1) { 290 return str; 291 } 292 var pos = 0; 293 var count = 0; // # of replacements made 294 295 while (nextIndex > -1 && (maxCount === -1 || count < maxCount)) { 296 // Grab the next chunk of src string and add it with the 297 // replacement, to the result 298 res += str.substring(pos, nextIndex) + new_; 299 // Increment our pointer in the src string 300 pos = nextIndex + old.length; 301 count++; 302 // See if there are any more replacements to be made 303 nextIndex = str.indexOf(old, pos); 304 } 305 306 // We've either reached the end, or done the max # of 307 // replacements, tack on any remaining string 308 if (pos < str.length) { 309 res += str.substring(pos); 310 } 311 return r.copySafeness(originalStr, res); 312} 313_exports.replace = replace; 314function reverse(val) { 315 var arr; 316 if (lib.isString(val)) { 317 arr = list(val); 318 } else { 319 // Copy it 320 arr = lib.map(val, function (v) { 321 return v; 322 }); 323 } 324 arr.reverse(); 325 if (lib.isString(val)) { 326 return r.copySafeness(val, arr.join('')); 327 } 328 return arr; 329} 330_exports.reverse = reverse; 331function round(val, precision, method) { 332 precision = precision || 0; 333 var factor = Math.pow(10, precision); 334 var rounder; 335 if (method === 'ceil') { 336 rounder = Math.ceil; 337 } else if (method === 'floor') { 338 rounder = Math.floor; 339 } else { 340 rounder = Math.round; 341 } 342 return rounder(val * factor) / factor; 343} 344_exports.round = round; 345function slice(arr, slices, fillWith) { 346 var sliceLength = Math.floor(arr.length / slices); 347 var extra = arr.length % slices; 348 var res = []; 349 var offset = 0; 350 for (var i = 0; i < slices; i++) { 351 var start = offset + i * sliceLength; 352 if (i < extra) { 353 offset++; 354 } 355 var end = offset + (i + 1) * sliceLength; 356 var currSlice = arr.slice(start, end); 357 if (fillWith && i >= extra) { 358 currSlice.push(fillWith); 359 } 360 res.push(currSlice); 361 } 362 return res; 363} 364_exports.slice = slice; 365function sum(arr, attr, start) { 366 if (start === void 0) { 367 start = 0; 368 } 369 if (attr) { 370 arr = lib.map(arr, function (v) { 371 return v[attr]; 372 }); 373 } 374 return start + arr.reduce(function (a, b) { 375 return a + b; 376 }, 0); 377} 378_exports.sum = sum; 379_exports.sort = r.makeMacro(['value', 'reverse', 'case_sensitive', 'attribute'], [], function sortFilter(arr, reversed, caseSens, attr) { 380 var _this = this; 381 // Copy it 382 var array = lib.map(arr, function (v) { 383 return v; 384 }); 385 var getAttribute = lib.getAttrGetter(attr); 386 array.sort(function (a, b) { 387 var x = attr ? getAttribute(a) : a; 388 var y = attr ? getAttribute(b) : b; 389 if (_this.env.opts.throwOnUndefined && attr && (x === undefined || y === undefined)) { 390 throw new TypeError("sort: attribute \"" + attr + "\" resolved to undefined"); 391 } 392 if (!caseSens && lib.isString(x) && lib.isString(y)) { 393 x = x.toLowerCase(); 394 y = y.toLowerCase(); 395 } 396 if (x < y) { 397 return reversed ? 1 : -1; 398 } else if (x > y) { 399 return reversed ? -1 : 1; 400 } else { 401 return 0; 402 } 403 }); 404 return array; 405}); 406function string(obj) { 407 return r.copySafeness(obj, obj); 408} 409_exports.string = string; 410function striptags(input, preserveLinebreaks) { 411 input = normalize(input, ''); 412 var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>|<!--[\s\S]*?-->/gi; 413 var trimmedInput = trim(input.replace(tags, '')); 414 var res = ''; 415 if (preserveLinebreaks) { 416 res = trimmedInput.replace(/^ +| +$/gm, '') // remove leading and trailing spaces 417 .replace(/ +/g, ' ') // squash adjacent spaces 418 .replace(/(\r\n)/g, '\n') // normalize linebreaks (CRLF -> LF) 419 .replace(/\n\n\n+/g, '\n\n'); // squash abnormal adjacent linebreaks 420 } else { 421 res = trimmedInput.replace(/\s+/gi, ' '); 422 } 423 return r.copySafeness(input, res); 424} 425_exports.striptags = striptags; 426function title(str) { 427 str = normalize(str, ''); 428 var words = str.split(' ').map(function (word) { 429 return capitalize(word); 430 }); 431 return r.copySafeness(str, words.join(' ')); 432} 433_exports.title = title; 434function trim(str) { 435 return r.copySafeness(str, str.replace(/^\s*|\s*$/g, '')); 436} 437_exports.trim = trim; 438function truncate(input, length, killwords, end) { 439 var orig = input; 440 input = normalize(input, ''); 441 length = length || 255; 442 if (input.length <= length) { 443 return input; 444 } 445 if (killwords) { 446 input = input.substring(0, length); 447 } else { 448 var idx = input.lastIndexOf(' ', length); 449 if (idx === -1) { 450 idx = length; 451 } 452 input = input.substring(0, idx); 453 } 454 input += end !== undefined && end !== null ? end : '...'; 455 return r.copySafeness(orig, input); 456} 457_exports.truncate = truncate; 458function upper(str) { 459 str = normalize(str, ''); 460 return str.toUpperCase(); 461} 462_exports.upper = upper; 463function urlencode(obj) { 464 var enc = encodeURIComponent; 465 if (lib.isString(obj)) { 466 return enc(obj); 467 } else { 468 var keyvals = lib.isArray(obj) ? obj : lib._entries(obj); 469 return keyvals.map(function (_ref2) { 470 var k = _ref2[0], 471 v = _ref2[1]; 472 return enc(k) + "=" + enc(v); 473 }).join('&'); 474 } 475} 476_exports.urlencode = urlencode; 477 478// For the jinja regexp, see 479// https://github.com/mitsuhiko/jinja2/blob/f15b814dcba6aa12bc74d1f7d0c881d55f7126be/jinja2/utils.py#L20-L23 480var puncRe = /^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/; 481// from http://blog.gerv.net/2011/05/html5_email_address_regexp/ 482var emailRe = /^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i; 483var httpHttpsRe = /^https?:\/\/.*$/; 484var wwwRe = /^www\./; 485var tldRe = /\.(?:org|net|com)(?:\:|\/|$)/; 486function urlize(str, length, nofollow) { 487 if (isNaN(length)) { 488 length = Infinity; 489 } 490 var noFollowAttr = nofollow === true ? ' rel="nofollow"' : ''; 491 var words = str.split(/(\s+)/).filter(function (word) { 492 // If the word has no length, bail. This can happen for str with 493 // trailing whitespace. 494 return word && word.length; 495 }).map(function (word) { 496 var matches = word.match(puncRe); 497 var possibleUrl = matches ? matches[1] : word; 498 var shortUrl = possibleUrl.substr(0, length); 499 500 // url that starts with http or https 501 if (httpHttpsRe.test(possibleUrl)) { 502 return "<a href=\"" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</a>"; 503 } 504 505 // url that starts with www. 506 if (wwwRe.test(possibleUrl)) { 507 return "<a href=\"http://" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</a>"; 508 } 509 510 // an email address of the form username@domain.tld 511 if (emailRe.test(possibleUrl)) { 512 return "<a href=\"mailto:" + possibleUrl + "\">" + possibleUrl + "</a>"; 513 } 514 515 // url that ends in .com, .org or .net that is not an email address 516 if (tldRe.test(possibleUrl)) { 517 return "<a href=\"http://" + possibleUrl + "\"" + noFollowAttr + ">" + shortUrl + "</a>"; 518 } 519 return word; 520 }); 521 return words.join(''); 522} 523_exports.urlize = urlize; 524function wordcount(str) { 525 str = normalize(str, ''); 526 var words = str ? str.match(/\w+/g) : null; 527 return words ? words.length : null; 528} 529_exports.wordcount = wordcount; 530function float(val, def) { 531 var res = parseFloat(val); 532 return isNaN(res) ? def : res; 533} 534_exports.float = float; 535var intFilter = r.makeMacro(['value', 'default', 'base'], [], function doInt(value, defaultValue, base) { 536 if (base === void 0) { 537 base = 10; 538 } 539 var res = parseInt(value, base); 540 return isNaN(res) ? defaultValue : res; 541}); 542_exports.int = intFilter; 543 544// Aliases 545_exports.d = _exports.default; 546_exports.e = _exports.escape;