Merge branch 'master' into grid-layout-expansion
This commit is contained in:
commit
daa464c4b5
|
@ -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,12 +47,25 @@ function testObject(object: any) {
|
|||
}
|
||||
}
|
||||
|
||||
function findOverlappingHelpers(context?: object) {
|
||||
if (!context) {
|
||||
return []
|
||||
}
|
||||
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 }
|
||||
const helpersEnabled = !opts?.noHelpers
|
||||
|
||||
// Finalising adds a helper, can't do this with no helpers
|
||||
const key = `${string}-${JSON.stringify(opts)}`
|
||||
|
@ -60,7 +75,25 @@ function createTemplate(string: string, opts?: ProcessOptions) {
|
|||
return templateCache[key]
|
||||
}
|
||||
|
||||
string = preprocess(string, opts)
|
||||
const overlappingHelpers = helpersEnabled
|
||||
? findOverlappingHelpers(context)
|
||||
: []
|
||||
|
||||
string = preprocess(string, {
|
||||
...opts,
|
||||
disabledHelpers: overlappingHelpers,
|
||||
})
|
||||
|
||||
if (context && helpersEnabled) {
|
||||
if (overlappingHelpers.length > 0) {
|
||||
for (const block of findHBSBlocks(string)) {
|
||||
string = string.replace(
|
||||
block,
|
||||
prefixStrings(block, overlappingHelpers, "./")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally disable built in HBS escaping
|
||||
if (opts.noEscaping) {
|
||||
|
@ -70,6 +103,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 +205,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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import { LITERAL_MARKER } from "../helpers/constants"
|
||||
|
||||
export const PostProcessorNames = {
|
||||
CONVERT_LITERALS: "convert-literals",
|
||||
export enum PostProcessorNames {
|
||||
CONVERT_LITERALS = "convert-literals",
|
||||
}
|
||||
|
||||
class Postprocessor {
|
||||
name: string
|
||||
private fn: any
|
||||
type PostprocessorFn = (statement: string) => string
|
||||
|
||||
constructor(name: string, fn: any) {
|
||||
class Postprocessor {
|
||||
name: PostProcessorNames
|
||||
private readonly fn: PostprocessorFn
|
||||
|
||||
constructor(name: PostProcessorNames, fn: PostprocessorFn) {
|
||||
this.name = name
|
||||
this.fn = fn
|
||||
}
|
||||
|
||||
process(statement: any) {
|
||||
process(statement: string) {
|
||||
return this.fn(statement)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
import { HelperNames } from "../helpers"
|
||||
import { swapStrings, isAlphaNumeric } from "../utilities"
|
||||
import { ProcessOptions } from "../types"
|
||||
|
||||
const FUNCTION_CASES = ["#", "else", "/"]
|
||||
|
||||
export const PreprocessorNames = {
|
||||
SWAP_TO_DOT: "swap-to-dot-notation",
|
||||
FIX_FUNCTIONS: "fix-functions",
|
||||
FINALISE: "finalise",
|
||||
NORMALIZE_SPACES: "normalize-spaces",
|
||||
export enum PreprocessorNames {
|
||||
SWAP_TO_DOT = "swap-to-dot-notation",
|
||||
FIX_FUNCTIONS = "fix-functions",
|
||||
FINALISE = "finalise",
|
||||
NORMALIZE_SPACES = "normalize-spaces",
|
||||
}
|
||||
|
||||
type PreprocessorFn = (statement: string, opts?: ProcessOptions) => string
|
||||
|
||||
class Preprocessor {
|
||||
name: string
|
||||
private fn: any
|
||||
private readonly fn: PreprocessorFn
|
||||
|
||||
constructor(name: string, fn: any) {
|
||||
constructor(name: PreprocessorNames, fn: PreprocessorFn) {
|
||||
this.name = name
|
||||
this.fn = fn
|
||||
}
|
||||
|
||||
process(fullString: string, statement: string, opts: Object) {
|
||||
process(fullString: string, statement: string, opts: ProcessOptions) {
|
||||
const output = this.fn(statement, opts)
|
||||
const idx = fullString.indexOf(statement)
|
||||
return swapStrings(fullString, idx, statement.length, output)
|
||||
|
@ -56,8 +59,9 @@ export const processors = [
|
|||
}),
|
||||
new Preprocessor(
|
||||
PreprocessorNames.FINALISE,
|
||||
(statement: string, opts: { noHelpers: any }) => {
|
||||
const noHelpers = opts && opts.noHelpers
|
||||
(statement: string, opts?: ProcessOptions) => {
|
||||
const noHelpers = opts?.noHelpers
|
||||
const helpersEnabled = !noHelpers
|
||||
let insideStatement = statement.slice(2, statement.length - 2)
|
||||
if (insideStatement.charAt(0) === " ") {
|
||||
insideStatement = insideStatement.slice(1)
|
||||
|
@ -74,7 +78,8 @@ export const processors = [
|
|||
}
|
||||
const testHelper = possibleHelper.trim().toLowerCase()
|
||||
if (
|
||||
!noHelpers &&
|
||||
helpersEnabled &&
|
||||
!opts?.disabledHelpers?.includes(testHelper) &&
|
||||
HelperNames().some(option => testHelper === option.toLowerCase())
|
||||
) {
|
||||
insideStatement = `(${insideStatement})`
|
||||
|
|
|
@ -5,4 +5,5 @@ export interface ProcessOptions {
|
|||
noFinalise?: boolean
|
||||
escapeNewlines?: boolean
|
||||
onlyFound?: boolean
|
||||
disabledHelpers?: string[]
|
||||
}
|
||||
|
|
|
@ -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`)
|
||||
}
|
||||
|
|
|
@ -483,3 +483,37 @@ describe("uuid", () => {
|
|||
expect(output).toMatch(UUID_REGEX)
|
||||
})
|
||||
})
|
||||
|
||||
describe("helper overlap", () => {
|
||||
it("should use context over helpers (regex test helper)", async () => {
|
||||
const output = await processString("{{ test }}", { test: "a" })
|
||||
expect(output).toEqual("a")
|
||||
})
|
||||
|
||||
it("should use helper if no sum in context, return the context value otherwise", async () => {
|
||||
const hbs = "{{ sum 1 2 }}"
|
||||
const output = await processString(hbs, {})
|
||||
expect(output).toEqual("3")
|
||||
const secondaryOutput = await processString(hbs, { sum: "a" })
|
||||
expect(secondaryOutput).toEqual("a")
|
||||
})
|
||||
|
||||
it("should handle multiple cases", async () => {
|
||||
const output = await processString("{{ literal (split test sum) }}", {
|
||||
test: "a-b",
|
||||
sum: "-",
|
||||
})
|
||||
expect(output).toEqual(["a", "b"])
|
||||
})
|
||||
|
||||
it("should work as expected when no helpers are set", async () => {
|
||||
const output = await processString(
|
||||
"{{ sum }}",
|
||||
{
|
||||
sum: "a",
|
||||
},
|
||||
{ noHelpers: true }
|
||||
)
|
||||
expect(output).toEqual("a")
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue