Skip to main content
Module

x/nunjucks/src/runtime.js

A powerful templating engine with inheritance, asynchronous control, and more (Jinja 2 inspired)
Go to Latest
File
import * as lib from "./lib.js";
const arrayFrom = Array.from;const supportsIterators = ( typeof Symbol === "function" && Symbol.iterator && typeof arrayFrom === "function");
// 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.class Frame { constructor(parent, isolateWrites) { this.variables = Object.create(null); this.parent = parent; this.topLevel = false; // if this is true, writes (set) should never propagate upwards past // this frame to its parent (though reads may). this.isolateWrites = isolateWrites; }
set(name, val, resolveUp) { // Allow variables with dots by automatically creating the // nested structure const parts = name.split("."); let obj = this.variables; let frame = this;
if (resolveUp) { if ((frame = this.resolve(parts[0], true))) { frame.set(name, val); return; } }
for (let i = 0; i < parts.length - 1; i++) { const id = parts[i];
if (!obj[id]) { obj[id] = {}; } obj = obj[id]; }
obj[parts[parts.length - 1]] = val; }
get(name) { const val = this.variables[name]; if (val !== undefined) { return val; } return null; }
lookup(name) { const p = this.parent; const val = this.variables[name]; if (val !== undefined) { return val; } return p && p.lookup(name); }
resolve(name, forWrite) { const p = (forWrite && this.isolateWrites) ? undefined : this.parent; const val = this.variables[name]; if (val !== undefined) { return this; } return p && p.resolve(name); }
push(isolateWrites) { return new Frame(this, isolateWrites); }
pop() { return this.parent; }}
function makeMacro(argNames, kwargNames, func) { return function macro(...macroArgs) { const argCount = numArgs(macroArgs); let args; const kwargs = getKeywordArgs(macroArgs);
if (argCount > argNames.length) { args = macroArgs.slice(0, argNames.length);
// Positional arguments that should be passed in as // keyword arguments (essentially default values) macroArgs.slice(args.length, argCount).forEach((val, i) => { if (i < kwargNames.length) { kwargs[kwargNames[i]] = val; } }); args.push(kwargs); } else if (argCount < argNames.length) { args = macroArgs.slice(0, argCount);
for (let i = argCount; i < argNames.length; i++) { const 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 = macroArgs; }
return func.apply(this, args); };}
function makeKeywordArgs(obj) { obj.__keywords = true; return obj;}
function isKeywordArgs(obj) { return obj && Object.prototype.hasOwnProperty.call(obj, "__keywords");}
function getKeywordArgs(args) { const len = args.length; if (len) { const lastArg = args[len - 1]; if (isKeywordArgs(lastArg)) { return lastArg; } } return {};}
function numArgs(args) { const len = args.length; if (len === 0) { return 0; }
const lastArg = args[len - 1]; if (isKeywordArgs(lastArg)) { 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.val = val; this.length = val.length;}
SafeString.prototype = Object.create(String.prototype, { length: { writable: true, configurable: true, value: 0, },});SafeString.prototype.valueOf = function valueOf() { return this.val;};SafeString.prototype.toString = function toString() { return this.val;};
function copySafeness(dest, target) { if (dest instanceof SafeString) { return new SafeString(target); } return target.toString();}
function markSafe(val) { const type = typeof val;
if (type === "string") { return new SafeString(val); } else if (type !== "function") { return val; } else { return function wrapSafe() { const 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 && !(val instanceof SafeString)) { val = lib.escape(val.toString()); }
return val;}
function ensureDefined(val, lineno, colno) { if (val === null || val === undefined) { throw new lib.TemplateError( "attempted to output null or undefined value", lineno + 1, colno + 1, ); } return val;}
function memberLookup(obj, val) { if (obj === undefined || obj === null) { return undefined; }
if (typeof obj[val] === "function") { return (...args) => obj[val].apply(obj, args); }
return obj[val];}
function callWrap(obj, name, context, 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(context, args);}
function contextOrFrameLookup(context, frame, name) { const val = frame.lookup(name); return (val !== undefined) ? 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)) { const len = arr.length;
lib.asyncIter(arr, function iterCallback(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, len, next); iter.apply(this, item); } }, cb); } else { lib.asyncFor(arr, function iterCallback(key, val, i, len, next) { iter(key, val, i, len, next); }, cb); }}
function asyncAll(arr, dimen, func, cb) { let finished = 0; let len; let 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 (let i = 0; i < arr.length; i++) { const 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, len, done); func.apply(this, item); } } } } else { const keys = lib.keys(arr || {}); len = keys.length; outputArr = new Array(len);
if (len === 0) { cb(null, ""); } else { for (let i = 0; i < keys.length; i++) { const k = keys[i]; func(k, arr[k], i, len, done); } } }}
function fromIterator(arr) { if (typeof arr !== "object" || arr === null || lib.isArray(arr)) { return arr; } else if (supportsIterators && Symbol.iterator in arr) { return arrayFrom(arr); } else { return arr; }}
const { inOperator, isArray, keys } = lib;export { asyncAll, asyncEach, callWrap, contextOrFrameLookup, copySafeness, ensureDefined, Frame, fromIterator, handleError, inOperator, isArray, keys, makeKeywordArgs, makeMacro, markSafe, memberLookup, numArgs, SafeString, suppressValue,};