Files

240 lines
6.7 KiB
JavaScript

"use strict"
// doT.js
// 2011-2014, Laura Doktorova, https://github.com/olado/doT
// Licensed under the MIT license.
const doT = {
templateSettings: {
argName: "it",
encoders: {},
selfContained: false,
strip: true,
internalPrefix: "_val",
encodersPrefix: "_enc",
delimiters: {
start: "{{",
end: "}}",
},
},
template,
compile,
setDelimiters,
}
module.exports = doT
// depends on selfContained mode
const encoderType = {
false: "function",
true: "string",
}
const defaultSyntax = {
evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g,
interpolate: /\{\{=([\s\S]+?)\}\}/g,
typeInterpolate: /\{\{%([nsb])=([\s\S]+?)\}\}/g,
encode: /\{\{([a-z_$]+[\w$]*)?!([\s\S]+?)\}\}/g,
use: /\{\{#([\s\S]+?)\}\}/g,
useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$]+(?:\.[\w$]+|\[[^\]]+\])*|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,
define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
defineParams: /^\s*([\w$]+):([\s\S]+)/,
conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
}
let currentSyntax = {...defaultSyntax}
const TYPES = {
n: "number",
s: "string",
b: "boolean",
}
function resolveDefs(c, syn, block, def) {
return (typeof block === "string" ? block : block.toString())
.replace(syn.define, (_, code, assign, value) => {
if (code.indexOf("def.") === 0) {
code = code.substring(4)
}
if (!(code in def)) {
if (assign === ":") {
value.replace(syn.defineParams, (_, param, v) => {
def[code] = {arg: param, text: v}
})
if (!(code in def)) def[code] = value
} else {
new Function("def", `def['${code}']=${value}`)(def)
}
}
return ""
})
.replace(syn.use, (_, code) => {
code = code.replace(syn.useParams, (_, s, d, param) => {
if (def[d] && def[d].arg && param) {
const rw = unescape((d + ":" + param).replace(/'|\\/g, "_"))
def.__exp = def.__exp || {}
def.__exp[rw] = def[d].text.replace(
new RegExp(`(^|[^\\w$])${def[d].arg}([^\\w$])`, "g"),
`$1${param}$2`
)
return s + `def.__exp['${rw}']`
}
})
const v = new Function("def", "return " + code)(def)
return v ? resolveDefs(c, syn, v, def) : v
})
}
function unescape(code) {
return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " ")
}
function template(tmpl, c, def) {
const ds = c && c.delimiters
const syn = ds && !sameDelimiters(ds) ? getSyntax(ds) : currentSyntax
c = c ? {...doT.templateSettings, ...c} : doT.templateSettings
let sid = 0
let str = resolveDefs(c, syn, tmpl, def || {})
const needEncoders = {}
str = (
"let out='" +
(c.strip
? str
.trim()
.replace(/[\t ]+(\r|\n)/g, "\n") // remove trailing spaces
.replace(/(\r|\n)[\t ]+/g, " ") // leading spaces reduced to " "
.replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g, "") // remove breaks, tabs and JS comments
: str
)
.replace(/'|\\/g, "\\$&")
.replace(syn.interpolate, (_, code) => `'+(${unescape(code)})+'`)
.replace(syn.typeInterpolate, (_, typ, code) => {
sid++
const val = c.internalPrefix + sid
const error = `throw new Error("expected ${TYPES[typ]}, got "+ (typeof ${val}))`
return `';const ${val}=(${unescape(code)});if(typeof ${val}!=="${
TYPES[typ]
}") ${error};out+=${val}+'`
})
.replace(syn.encode, (_, enc = "", code) => {
needEncoders[enc] = true
code = unescape(code)
const e = c.selfContained ? enc : enc ? "." + enc : '[""]'
return `'+${c.encodersPrefix}${e}(${code})+'`
})
.replace(syn.conditional, (_, elseCase, code) => {
if (code) {
code = unescape(code)
return elseCase ? `';}else if(${code}){out+='` : `';if(${code}){out+='`
}
return elseCase ? "';}else{out+='" : "';}out+='"
})
.replace(syn.iterate, (_, arr, vName, iName) => {
if (!arr) return "';} } out+='"
sid++
const defI = iName ? `let ${iName}=-1;` : ""
const incI = iName ? `${iName}++;` : ""
const val = c.internalPrefix + sid
return `';const ${val}=${unescape(
arr
)};if(${val}){${defI}for (const ${vName} of ${val}){${incI}out+='`
})
.replace(syn.evaluate, (_, code) => `';${unescape(code)}out+='`) +
"';return out;"
)
.replace(/\n/g, "\\n")
.replace(/\t/g, "\\t")
.replace(/\r/g, "\\r")
.replace(/(\s|;|\}|^|\{)out\+='';/g, "$1")
.replace(/\+''/g, "")
const args = Array.isArray(c.argName) ? properties(c.argName) : c.argName
if (Object.keys(needEncoders).length === 0) {
return try_(() => new Function(args, str))
}
checkEncoders(c, needEncoders)
str = `return function(${args}){${str}};`
return try_(() =>
c.selfContained
? new Function((str = addEncoders(c, needEncoders) + str))()
: new Function(c.encodersPrefix, str)(c.encoders)
)
function try_(f) {
try {
return f()
} catch (e) {
console.log("Could not create a template function: " + str)
throw e
}
}
}
function compile(tmpl, def) {
return template(tmpl, null, def)
}
function sameDelimiters({start, end}) {
const d = doT.templateSettings.delimiters
return d.start === start && d.end === end
}
function setDelimiters(delimiters) {
if (sameDelimiters(delimiters)) {
console.log("delimiters did not change")
return
}
currentSyntax = getSyntax(delimiters)
doT.templateSettings.delimiters = delimiters
}
function getSyntax({start, end}) {
start = escape(start)
end = escape(end)
const syntax = {}
for (const syn in defaultSyntax) {
const s = defaultSyntax[syn]
.toString()
.replace(/\\\{\\\{/g, start)
.replace(/\\\}\\\}/g, end)
syntax[syn] = strToRegExp(s)
}
return syntax
}
const escapeCharacters = /([{}[\]()<>\\\/^$\-.+*?!=|&:])/g
function escape(str) {
return str.replace(escapeCharacters, "\\$1")
}
const regexpPattern = /^\/(.*)\/([\w]*)$/
function strToRegExp(str) {
const [, rx, flags] = str.match(regexpPattern)
return new RegExp(rx, flags)
}
function properties(args) {
return args.reduce((s, a, i) => s + (i ? "," : "") + a, "{") + "}"
}
function checkEncoders(c, encoders) {
const typ = encoderType[c.selfContained]
for (const enc in encoders) {
const e = c.encoders[enc]
if (!e) throw new Error(`unknown encoder "${enc}"`)
if (typeof e !== typ)
throw new Error(`selfContained ${c.selfContained}: encoder type must be "${typ}"`)
}
}
function addEncoders(c, encoders) {
let s = ""
for (const enc in encoders) s += `const ${c.encodersPrefix}${enc}=${c.encoders[enc]};`
return s
}