This commit is contained in:
Adria Navarro 2024-02-22 11:16:09 +01:00
parent 9e0e9297f5
commit c3531ac8ba
6 changed files with 118 additions and 86 deletions

View File

@ -1,11 +1,11 @@
import { getJsHelperList } from "../helpers" import { getJsHelperList } from "../helpers"
function getLayers(fullBlock) { function getLayers(fullBlock: string): string[] {
let layers = [] let layers = []
while (fullBlock.length) { while (fullBlock.length) {
const start = fullBlock.lastIndexOf("("), const start = fullBlock.lastIndexOf("("),
end = fullBlock.indexOf(")") end = fullBlock.indexOf(")")
let layer let layer: string
if (start === -1 || end === -1) { if (start === -1 || end === -1) {
layer = fullBlock.trim() layer = fullBlock.trim()
fullBlock = "" fullBlock = ""
@ -21,7 +21,7 @@ function getLayers(fullBlock) {
return layers return layers
} }
function getVariable(variableName) { function getVariable(variableName: string) {
if (!variableName || typeof variableName !== "string") { if (!variableName || typeof variableName !== "string") {
return variableName return variableName
} }
@ -47,10 +47,12 @@ function getVariable(variableName) {
return `$("${variableName}")` return `$("${variableName}")`
} }
function buildList(parts, value) { function buildList(parts: any[], value: any) {
function build() { function build() {
return parts return parts
.map(part => (part.startsWith("helper") ? part : getVariable(part))) .map((part: string) =>
part.startsWith("helper") ? part : getVariable(part)
)
.join(", ") .join(", ")
} }
if (!value) { if (!value) {
@ -60,12 +62,12 @@ function buildList(parts, value) {
} }
} }
function splitBySpace(layer) { function splitBySpace(layer: string) {
const parts = [] const parts = []
let started = null, let started = null,
endChar = null, endChar = null,
last = 0 last = 0
function add(str) { function add(str: string) {
const startsWith = ["]"] const startsWith = ["]"]
while (startsWith.indexOf(str.substring(0, 1)) !== -1) { while (startsWith.indexOf(str.substring(0, 1)) !== -1) {
str = str.substring(1, str.length) str = str.substring(1, str.length)
@ -103,7 +105,7 @@ function splitBySpace(layer) {
return parts return parts
} }
export function convertHBSBlock(block, blockNumber) { export function convertHBSBlock(block: string, blockNumber: number) {
const braceLength = block[2] === "{" ? 3 : 2 const braceLength = block[2] === "{" ? 3 : 2
block = block.substring(braceLength, block.length - braceLength).trim() block = block.substring(braceLength, block.length - braceLength).trim()
const layers = getLayers(block) const layers = getLayers(block)

View File

@ -1,4 +1,4 @@
import { createContext, runInNewContext } from "vm" import { Context, createContext, runInNewContext } from "vm"
import { create } from "handlebars" import { create } from "handlebars"
import { registerAll, registerMinimum } from "./helpers/index" import { registerAll, registerMinimum } from "./helpers/index"
import { preprocess, postprocess } from "./processors" import { preprocess, postprocess } from "./processors"
@ -15,6 +15,7 @@ import { setJSRunner, removeJSRunner } from "./helpers/javascript"
import { helpersToRemoveForJs } from "./helpers/list" import { helpersToRemoveForJs } from "./helpers/list"
import manifest from "../manifest.json" import manifest from "../manifest.json"
import { ProcessOptions } from "./types"
export { setJSRunner, setOnErrorLog } from "./helpers/javascript" export { setJSRunner, setOnErrorLog } from "./helpers/javascript"
@ -22,7 +23,7 @@ const hbsInstance = create()
registerAll(hbsInstance) registerAll(hbsInstance)
const hbsInstanceNoHelpers = create() const hbsInstanceNoHelpers = create()
registerMinimum(hbsInstanceNoHelpers) registerMinimum(hbsInstanceNoHelpers)
const defaultOpts = { const defaultOpts: ProcessOptions = {
noHelpers: false, noHelpers: false,
cacheTemplates: false, cacheTemplates: false,
noEscaping: false, noEscaping: false,
@ -33,7 +34,7 @@ const defaultOpts = {
/** /**
* Utility function to check if the object is valid. * Utility function to check if the object is valid.
*/ */
function testObject(object) { function testObject(object: any) {
// JSON stringify will fail if there are any cycles, stops infinite recursion // JSON stringify will fail if there are any cycles, stops infinite recursion
try { try {
JSON.stringify(object) JSON.stringify(object)
@ -46,7 +47,7 @@ function testObject(object) {
* Creates a HBS template function for a given string, and optionally caches it. * Creates a HBS template function for a given string, and optionally caches it.
*/ */
let templateCache = {} let templateCache = {}
function createTemplate(string, opts) { function createTemplate(string: string, opts: ProcessOptions) {
opts = { ...defaultOpts, ...opts } opts = { ...defaultOpts, ...opts }
// Finalising adds a helper, can't do this with no helpers // Finalising adds a helper, can't do this with no helpers
@ -82,7 +83,11 @@ function createTemplate(string, opts) {
* @param {object|undefined} [opts] optional - specify some options for processing. * @param {object|undefined} [opts] optional - specify some options for processing.
* @returns {Promise<object|array>} The structure input, as fully updated as possible. * @returns {Promise<object|array>} The structure input, as fully updated as possible.
*/ */
export async function processObject(object, context, opts?) { export async function processObject(
object: { [x: string]: any },
context: object,
opts?: { noHelpers?: boolean; escapeNewlines?: boolean; onlyFound?: boolean }
) {
testObject(object) testObject(object)
for (let key of Object.keys(object || {})) { for (let key of Object.keys(object || {})) {
if (object[key] != null) { if (object[key] != null) {
@ -123,7 +128,11 @@ export async function processString(
* @param {object|undefined} [opts] optional - specify some options for processing. * @param {object|undefined} [opts] optional - specify some options for processing.
* @returns {object|array} The structure input, as fully updated as possible. * @returns {object|array} The structure input, as fully updated as possible.
*/ */
export function processObjectSync(object, context, opts) { export function processObjectSync(
object: { [x: string]: any },
context: any,
opts: any
) {
testObject(object) testObject(object)
for (let key of Object.keys(object || {})) { for (let key of Object.keys(object || {})) {
let val = object[key] let val = object[key]
@ -144,13 +153,17 @@ export function processObjectSync(object, context, opts) {
* @param {object|undefined} [opts] optional - specify some options for processing. * @param {object|undefined} [opts] optional - specify some options for processing.
* @returns {string} The enriched string, all templates should have been replaced if they can be. * @returns {string} The enriched string, all templates should have been replaced if they can be.
*/ */
export function processStringSync(string, context, opts?) { export function processStringSync(
string: string,
context: object,
opts?: { noHelpers?: boolean; escapeNewlines?: boolean; onlyFound: any }
) {
// Take a copy of input in case of error // Take a copy of input in case of error
const input = string const input = string
if (typeof string !== "string") { if (typeof string !== "string") {
throw "Cannot process non-string types." throw "Cannot process non-string types."
} }
function process(stringPart) { function process(stringPart: string) {
const template = createTemplate(stringPart, opts) const template = createTemplate(stringPart, opts)
const now = Math.floor(Date.now() / 1000) * 1000 const now = Math.floor(Date.now() / 1000) * 1000
return postprocess( return postprocess(
@ -186,7 +199,7 @@ export function processStringSync(string, context, opts?) {
* this function will find any double braces and switch to triple. * this function will find any double braces and switch to triple.
* @param string the string to have double HBS statements converted to triple. * @param string the string to have double HBS statements converted to triple.
*/ */
export function disableEscaping(string) { export function disableEscaping(string: string) {
const matches = findDoubleHbsInstances(string) const matches = findDoubleHbsInstances(string)
if (matches == null) { if (matches == null) {
return string return string
@ -207,7 +220,7 @@ export function disableEscaping(string) {
* @param {string} property The property which is to be wrapped. * @param {string} property The property which is to be wrapped.
* @returns {string} The wrapped property ready to be added to a templating string. * @returns {string} The wrapped property ready to be added to a templating string.
*/ */
export function makePropSafe(property) { export function makePropSafe(property: any) {
return `[${property}]`.replace("[[", "[").replace("]]", "]") return `[${property}]`.replace("[[", "[").replace("]]", "]")
} }
@ -217,7 +230,7 @@ export function makePropSafe(property) {
* @param [opts] optional - specify some options for processing. * @param [opts] optional - specify some options for processing.
* @returns {boolean} Whether or not the input string is valid. * @returns {boolean} Whether or not the input string is valid.
*/ */
export function isValid(string, opts?) { export function isValid(string: any, opts?: any) {
const validCases = [ const validCases = [
"string", "string",
"number", "number",
@ -268,7 +281,7 @@ export function getManifest() {
* @param handlebars the HBS expression to check * @param handlebars the HBS expression to check
* @returns {boolean} whether the expression is JS or not * @returns {boolean} whether the expression is JS or not
*/ */
export function isJSBinding(handlebars) { export function isJSBinding(handlebars: any) {
return decodeJSBinding(handlebars) != null return decodeJSBinding(handlebars) != null
} }
@ -277,7 +290,7 @@ export function isJSBinding(handlebars) {
* @param javascript the JS code to encode * @param javascript the JS code to encode
* @returns {string} the JS HBS expression * @returns {string} the JS HBS expression
*/ */
export function encodeJSBinding(javascript) { export function encodeJSBinding(javascript: string) {
return `{{ js "${btoa(javascript)}" }}` return `{{ js "${btoa(javascript)}" }}`
} }
@ -286,7 +299,7 @@ export function encodeJSBinding(javascript) {
* @param handlebars the JS HBS expression * @param handlebars the JS HBS expression
* @returns {string|null} the raw JS code * @returns {string|null} the raw JS code
*/ */
export function decodeJSBinding(handlebars) { export function decodeJSBinding(handlebars: string) {
if (!handlebars || typeof handlebars !== "string") { if (!handlebars || typeof handlebars !== "string") {
return null return null
} }
@ -311,7 +324,7 @@ export function decodeJSBinding(handlebars) {
* @param {string[]} strings The strings to look for. * @param {string[]} strings The strings to look for.
* @returns {boolean} Will return true if all strings found in HBS statement. * @returns {boolean} Will return true if all strings found in HBS statement.
*/ */
export function doesContainStrings(template, strings) { export function doesContainStrings(template: string, strings: any[]) {
let regexp = new RegExp(FIND_HBS_REGEX) let regexp = new RegExp(FIND_HBS_REGEX)
let matches = template.match(regexp) let matches = template.match(regexp)
if (matches == null) { if (matches == null) {
@ -341,7 +354,7 @@ export function doesContainStrings(template, strings) {
* @param {string} string The string to search within. * @param {string} string The string to search within.
* @return {string[]} The found HBS blocks. * @return {string[]} The found HBS blocks.
*/ */
export function findHBSBlocks(string) { export function findHBSBlocks(string: string) {
if (!string || typeof string !== "string") { if (!string || typeof string !== "string") {
return [] return []
} }
@ -362,11 +375,11 @@ export function findHBSBlocks(string) {
* @param {string} string The word or sentence to search for. * @param {string} string The word or sentence to search for.
* @returns {boolean} The this return true if the string is found, false if not. * @returns {boolean} The this return true if the string is found, false if not.
*/ */
export function doesContainString(template, string) { export function doesContainString(template: any, string: any) {
return doesContainStrings(template, [string]) return doesContainStrings(template, [string])
} }
export function convertToJS(hbs) { export function convertToJS(hbs: string) {
const blocks = findHBSBlocks(hbs) const blocks = findHBSBlocks(hbs)
let js = "return `", let js = "return `",
prevBlock: string | null = null prevBlock: string | null = null
@ -407,7 +420,7 @@ function defaultJSSetup() {
/** /**
* Use polyfilled vm to run JS scripts in a browser Env * Use polyfilled vm to run JS scripts in a browser Env
*/ */
setJSRunner((js, context) => { setJSRunner((js: string, context: Context) => {
context = { context = {
...context, ...context,
alert: undefined, alert: undefined,

View File

@ -1,8 +1,9 @@
import { FIND_HBS_REGEX } from "../utilities" import { FIND_HBS_REGEX } from "../utilities"
import * as preprocessor from "./preprocessor" import * as preprocessor from "./preprocessor"
import * as postprocessor from "./postprocessor" import * as postprocessor from "./postprocessor"
import { ProcessOptions } from "../types"
function process(output, processors, opts?) { function process(output: string, processors: any[], opts?: ProcessOptions) {
for (let processor of processors) { for (let processor of processors) {
// if a literal statement has occurred stop // if a literal statement has occurred stop
if (typeof output !== "string") { if (typeof output !== "string") {
@ -21,7 +22,7 @@ function process(output, processors, opts?) {
return output return output
} }
export function preprocess(string, opts) { export function preprocess(string: string, opts: ProcessOptions) {
let processors = preprocessor.processors let processors = preprocessor.processors
if (opts.noFinalise) { if (opts.noFinalise) {
processors = processors.filter( processors = processors.filter(
@ -30,7 +31,7 @@ export function preprocess(string, opts) {
} }
return process(string, processors, opts) return process(string, processors, opts)
} }
export function postprocess(string) { export function postprocess(string: string) {
let processors = postprocessor.processors let processors = postprocessor.processors
return process(string, processors) return process(string, processors)
} }

View File

@ -6,45 +6,51 @@ export const PostProcessorNames = {
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
class Postprocessor { class Postprocessor {
name name: string
private fn private fn: any
constructor(name, fn) { constructor(name: string, fn: any) {
this.name = name this.name = name
this.fn = fn this.fn = fn
} }
process(statement) { process(statement: any) {
return this.fn(statement) return this.fn(statement)
} }
} }
export const processors = [ export const processors = [
new Postprocessor(PostProcessorNames.CONVERT_LITERALS, statement => { new Postprocessor(
if (typeof statement !== "string" || !statement.includes(LITERAL_MARKER)) { PostProcessorNames.CONVERT_LITERALS,
return statement (statement: string) => {
if (
typeof statement !== "string" ||
!statement.includes(LITERAL_MARKER)
) {
return statement
}
const splitMarkerIndex = statement.indexOf("-")
const type = statement.substring(12, splitMarkerIndex)
const value = statement.substring(
splitMarkerIndex + 1,
statement.length - 2
)
switch (type) {
case "string":
return value
case "number":
return parseFloat(value)
case "boolean":
return value === "true"
case "object":
return JSON.parse(value)
case "js_result":
// We use the literal helper to process the result of JS expressions
// as we want to be able to return any types.
// We wrap the value in an abject to be able to use undefined properly.
return JSON.parse(value).data
}
return value
} }
const splitMarkerIndex = statement.indexOf("-") ),
const type = statement.substring(12, splitMarkerIndex)
const value = statement.substring(
splitMarkerIndex + 1,
statement.length - 2
)
switch (type) {
case "string":
return value
case "number":
return parseFloat(value)
case "boolean":
return value === "true"
case "object":
return JSON.parse(value)
case "js_result":
// We use the literal helper to process the result of JS expressions
// as we want to be able to return any types.
// We wrap the value in an abject to be able to use undefined properly.
return JSON.parse(value).data
}
return value
}),
] ]

View File

@ -14,7 +14,7 @@ class Preprocessor {
name: string name: string
private fn: any private fn: any
constructor(name, fn) { constructor(name: string, fn: any) {
this.name = name this.name = name
this.fn = fn this.fn = fn
} }
@ -27,7 +27,7 @@ class Preprocessor {
} }
export const processors = [ export const processors = [
new Preprocessor(PreprocessorNames.SWAP_TO_DOT, statement => { new Preprocessor(PreprocessorNames.SWAP_TO_DOT, (statement: string) => {
let startBraceIdx = statement.indexOf("[") let startBraceIdx = statement.indexOf("[")
let lastIdx = 0 let lastIdx = 0
while (startBraceIdx !== -1) { while (startBraceIdx !== -1) {
@ -42,7 +42,7 @@ export const processors = [
return statement return statement
}), }),
new Preprocessor(PreprocessorNames.FIX_FUNCTIONS, statement => { new Preprocessor(PreprocessorNames.FIX_FUNCTIONS, (statement: string) => {
for (let specialCase of FUNCTION_CASES) { for (let specialCase of FUNCTION_CASES) {
const toFind = `{ ${specialCase}`, const toFind = `{ ${specialCase}`,
replacement = `{${specialCase}` replacement = `{${specialCase}`
@ -51,29 +51,32 @@ export const processors = [
return statement return statement
}), }),
new Preprocessor(PreprocessorNames.FINALISE, (statement, opts) => { new Preprocessor(
const noHelpers = opts && opts.noHelpers PreprocessorNames.FINALISE,
let insideStatement = statement.slice(2, statement.length - 2) (statement: string, opts: { noHelpers: any }) => {
if (insideStatement.charAt(0) === " ") { const noHelpers = opts && opts.noHelpers
insideStatement = insideStatement.slice(1) let insideStatement = statement.slice(2, statement.length - 2)
} if (insideStatement.charAt(0) === " ") {
if (insideStatement.charAt(insideStatement.length - 1) === " ") { insideStatement = insideStatement.slice(1)
insideStatement = insideStatement.slice(0, insideStatement.length - 1)
}
const possibleHelper = insideStatement.split(" ")[0]
// function helpers can't be wrapped
for (let specialCase of FUNCTION_CASES) {
if (possibleHelper.includes(specialCase)) {
return statement
} }
if (insideStatement.charAt(insideStatement.length - 1) === " ") {
insideStatement = insideStatement.slice(0, insideStatement.length - 1)
}
const possibleHelper = insideStatement.split(" ")[0]
// function helpers can't be wrapped
for (let specialCase of FUNCTION_CASES) {
if (possibleHelper.includes(specialCase)) {
return statement
}
}
const testHelper = possibleHelper.trim().toLowerCase()
if (
!noHelpers &&
HelperNames().some(option => testHelper === option.toLowerCase())
) {
insideStatement = `(${insideStatement})`
}
return `{{ all ${insideStatement} }}`
} }
const testHelper = possibleHelper.trim().toLowerCase() ),
if (
!noHelpers &&
HelperNames().some(option => testHelper === option.toLowerCase())
) {
insideStatement = `(${insideStatement})`
}
return `{{ all ${insideStatement} }}`
}),
] ]

View File

@ -0,0 +1,7 @@
export interface ProcessOptions {
cacheTemplates?: boolean
noEscaping?: boolean
noHelpers?: boolean
noFinalise?: boolean
escapeNewlines?: boolean
}