1
0
Fork 0
mirror of synced 2024-09-20 11:27:56 +12:00

First pass - no tests yet, had to make some changes to how pre-processing works, as well as updating the string based on context, if there is any overlap between the helpers and context it will prefix the overlap with ./ - this means to look in context.

This commit is contained in:
Michael Drury 2024-08-14 17:21:40 +01:00
parent 4623fd406e
commit 165eff2e5a
5 changed files with 62 additions and 11 deletions

View file

@ -1,17 +1,18 @@
import { Context, createContext, runInNewContext } from "vm"
import { create, TemplateDelegate } from "handlebars"
import { registerAll, registerMinimum } from "./helpers/index"
import { preprocess, postprocess } from "./processors"
import { postprocess, preprocess } from "./processors"
import {
atob,
btoa,
isBackendService,
FIND_HBS_REGEX,
FIND_ANY_HBS_REGEX,
FIND_HBS_REGEX,
findDoubleHbsInstances,
isBackendService,
prefixStrings,
} from "./utilities"
import { convertHBSBlock } from "./conversion"
import { setJSRunner, removeJSRunner } from "./helpers/javascript"
import { removeJSRunner, setJSRunner } from "./helpers/javascript"
import manifest from "./manifest.json"
import { ProcessOptions } from "./types"
@ -23,6 +24,7 @@ export { iifeWrapper } from "./iife"
const hbsInstance = create()
registerAll(hbsInstance)
const helperNames = Object.keys(hbsInstance.helpers)
const hbsInstanceNoHelpers = create()
registerMinimum(hbsInstanceNoHelpers)
const defaultOpts: ProcessOptions = {
@ -45,11 +47,20 @@ function testObject(object: any) {
}
}
function findOverlappingHelpers(context: object) {
const contextKeys = Object.keys(context)
return contextKeys.filter(key => helperNames.includes(key))
}
/**
* Creates a HBS template function for a given string, and optionally caches it.
*/
const templateCache: Record<string, TemplateDelegate<any>> = {}
function createTemplate(string: string, opts?: ProcessOptions) {
function createTemplate(
string: string,
opts?: ProcessOptions,
context?: object
) {
opts = { ...defaultOpts, ...opts }
// Finalising adds a helper, can't do this with no helpers
@ -60,7 +71,25 @@ function createTemplate(string: string, opts?: ProcessOptions) {
return templateCache[key]
}
string = preprocess(string, opts)
const overlappingHelpers = !opts?.noHelpers
? findOverlappingHelpers(context)
: []
string = preprocess(string, {
...opts,
disabledHelpers: overlappingHelpers,
})
if (context && !opts?.noHelpers) {
if (overlappingHelpers.length > 0) {
for (let block of findHBSBlocks(string)) {
string = string.replace(
block,
prefixStrings(block, overlappingHelpers, "./")
)
}
}
}
// Optionally disable built in HBS escaping
if (opts.noEscaping) {
@ -70,6 +99,7 @@ function createTemplate(string: string, opts?: ProcessOptions) {
// This does not throw an error when template can't be fulfilled,
// have to try correct beforehand
const instance = opts.noHelpers ? hbsInstanceNoHelpers : hbsInstance
const template = instance.compile(string, {
strict: false,
})
@ -171,7 +201,8 @@ export function processStringSync(
throw "Cannot process non-string types."
}
function process(stringPart: string) {
const template = createTemplate(stringPart, opts)
// context is needed to check for overlap between helpers and context
const template = createTemplate(stringPart, opts, context)
const now = Math.floor(Date.now() / 1000) * 1000
const processedString = template({
now: new Date(now).toISOString(),

View file

@ -29,9 +29,9 @@ export function preprocess(string: string, opts: ProcessOptions) {
processor => processor.name !== preprocessor.PreprocessorNames.FINALISE
)
}
return process(string, processors, opts)
}
export function postprocess(string: string) {
let processors = postprocessor.processors
return process(string, processors)
return process(string, postprocessor.processors)
}

View file

@ -13,10 +13,12 @@ export const PreprocessorNames = {
class Preprocessor {
name: string
private fn: any
private helperNames: string[]
constructor(name: string, fn: any) {
this.name = name
this.fn = fn
this.helperNames = HelperNames()
}
process(fullString: string, statement: string, opts: Object) {
@ -56,7 +58,10 @@ export const processors = [
}),
new Preprocessor(
PreprocessorNames.FINALISE,
(statement: string, opts: { noHelpers: any }) => {
(
statement: string,
opts: { noHelpers: any; disabledHelpers?: string[] }
) => {
const noHelpers = opts && opts.noHelpers
let insideStatement = statement.slice(2, statement.length - 2)
if (insideStatement.charAt(0) === " ") {
@ -75,7 +80,8 @@ export const processors = [
const testHelper = possibleHelper.trim().toLowerCase()
if (
!noHelpers &&
HelperNames().some(option => testHelper === option.toLowerCase())
!opts.disabledHelpers?.includes(testHelper) &&
this.helperNames.some(option => testHelper === option.toLowerCase())
) {
insideStatement = `(${insideStatement})`
}

View file

@ -5,4 +5,5 @@ export interface ProcessOptions {
noFinalise?: boolean
escapeNewlines?: boolean
onlyFound?: boolean
disabledHelpers?: string[]
}

View file

@ -66,3 +66,16 @@ export const btoa = (plainText: string) => {
export const atob = (base64: string) => {
return Buffer.from(base64, "base64").toString("utf-8")
}
export const prefixStrings = (
baseString: string,
strings: string[],
prefix: string
) => {
// Escape any special characters in the strings to avoid regex errors
const escapedStrings = strings.map(str =>
str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
)
const regexPattern = new RegExp(`\\b(${escapedStrings.join("|")})\\b`, "g")
return baseString.replace(regexPattern, `${prefix}$1`)
}