Adding cleaners which will process and find spaces removing them and fixing them with literal specifiers for handlebars props. Also changing the way cleaners work for the system to make it easier to add them.

This commit is contained in:
mike12345567 2021-01-20 18:12:16 +00:00
parent 976517e028
commit da457cd0c4
4 changed files with 125 additions and 49 deletions

View File

@ -0,0 +1,91 @@
const { HelperFunctions } = require("./helpers/index")
const HBS_CLEANING_REGEX = /{{[^}}]*}}/g
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
function isAlphaNumeric(char) {
return char.match(ALPHA_NUMERIC_REGEX)
}
function swapStrings(string, start, length, swap) {
return string.slice(0, start) + swap + string.slice(start + length)
}
function handleCleaner(string, match, fn) {
const output = fn(match)
const idx = string.indexOf(match)
return swapStrings(string, idx, match.length, output)
}
function swapToDotNotation(statement) {
let startBraceIdx = statement.indexOf("[")
let lastIdx = 0
while (startBraceIdx !== -1) {
// if the character previous to the literal specifier is alpha-numeric this should happen
if (isAlphaNumeric(statement.charAt(startBraceIdx - 1))) {
statement = swapStrings(statement, startBraceIdx + lastIdx, 1, ".[")
}
lastIdx = startBraceIdx + 1
startBraceIdx = statement.substring(lastIdx + 1).indexOf("[")
}
return statement
}
function handleSpacesInProperties(statement) {
// exclude helpers and brackets, regex will only find double brackets
const exclusions = HelperFunctions.concat(["{{", "}}"])
// find all the parts split by spaces
const splitBySpaces = statement.split(" ")
// remove the excluded elements
const propertyParts = splitBySpaces.filter(part => exclusions.indexOf(part) === -1)
// rebuild to get the full property
const fullProperty = propertyParts.join(" ")
// now work out the dot notation layers and split them up
const propertyLayers = fullProperty.split(".")
// find the layers which need to be wrapped and wrap them
for (let layer of propertyLayers) {
if (layer.indexOf(" ") !== -1) {
statement = swapStrings(statement, statement.indexOf(layer), layer.length, `[${layer}]`)
}
}
// remove the edge case of double brackets being entered (in-case user already has specified)
return statement.replace(/\[\[/g, "[").replace(/]]/g, "]")
}
function finalise(statement) {
let insideStatement = statement.slice(2, statement.length - 2)
if (insideStatement.charAt(0) === " ") {
insideStatement = insideStatement.slice(1)
}
if (insideStatement.charAt(insideStatement.length - 1) === " ") {
insideStatement = insideStatement.slice(0, insideStatement.length - 1)
}
return `{{ all (${insideStatement}) }}`
}
/**
* When running handlebars statements to execute on the context of the automation it possible user's may input handlebars
* in a few different forms, some of which are invalid but are logically valid. An example of this would be the handlebars
* statement "{{steps[0].revision}}" here it is obvious the user is attempting to access an array or object using array
* like operators. These are not supported by handlebars and therefore the statement will fail. This function will clean up
* the handlebars statement so it instead reads as "{{steps.0.revision}}" which is valid and will work. It may also be expanded
* to include any other handlebars statement cleanup that has been deemed necessary for the system.
*
* @param {string} string The string which *may* contain handlebars statements, it is OK if it does not contain any.
* @returns {string} The string that was input with cleaned up handlebars statements as required.
*/
module.exports.cleanHandlebars = (string) => {
let cleaners = [swapToDotNotation, handleSpacesInProperties, finalise]
for (let cleaner of cleaners) {
// re-run search each time incase previous cleaner update/removed a match
let regex = new RegExp(HBS_CLEANING_REGEX)
let matches = string.match(regex)
if (matches == null) {
continue
}
for (let match of matches) {
string = handleCleaner(string, match, cleaner)
}
}
return string
}

View File

@ -6,14 +6,28 @@ const HTML_SWAPS = {
">": ">", ">": ">",
} }
const HelperFunctionBuiltin = [
"#if",
"#unless",
"#each",
"#with",
"lookup",
"log"
]
const HelperFunctionNames = {
OBJECT: "object",
ALL: "all",
}
const HELPERS = [ const HELPERS = [
// external helpers // external helpers
new Helper("object", value => { new Helper(HelperFunctionNames.OBJECT, value => {
return new SafeString(JSON.stringify(value)) return new SafeString(JSON.stringify(value))
}), }),
// this help is applied to all statements // this help is applied to all statements
new Helper("all", value => { new Helper(HelperFunctionNames.ALL, value => {
let text = unescape(value).replace(/&/g, '&'); let text = new SafeString(unescape(value).replace(/&/g, '&'))
if (text == null || typeof text !== "string") { if (text == null || typeof text !== "string") {
return text return text
} }
@ -23,6 +37,8 @@ const HELPERS = [
}) })
] ]
module.exports.HelperFunctions = Object.values(HelperFunctionNames).concat(HelperFunctionBuiltin)
module.exports.registerAll = handlebars => { module.exports.registerAll = handlebars => {
for (let helper of HELPERS) { for (let helper of HELPERS) {
helper.register(handlebars) helper.register(handlebars)

View File

@ -1,54 +1,10 @@
const handlebars = require("handlebars") const handlebars = require("handlebars")
const { registerAll } = require("./helpers/index") const { registerAll } = require("./helpers/index")
const { cleanHandlebars } = require("./cleaning")
const HBS_CLEANING_REGEX = /{{[^}}]*}}/g
const FIND_HBS_REGEX = /{{.*}}/
const hbsInstance = handlebars.create() const hbsInstance = handlebars.create()
registerAll(hbsInstance) registerAll(hbsInstance)
/**
* When running handlebars statements to execute on the context of the automation it possible user's may input handlebars
* in a few different forms, some of which are invalid but are logically valid. An example of this would be the handlebars
* statement "{{steps[0].revision}}" here it is obvious the user is attempting to access an array or object using array
* like operators. These are not supported by handlebars and therefore the statement will fail. This function will clean up
* the handlebars statement so it instead reads as "{{steps.0.revision}}" which is valid and will work. It may also be expanded
* to include any other handlebars statement cleanup that has been deemed necessary for the system.
*
* @param {string} string The string which *may* contain handlebars statements, it is OK if it does not contain any.
* @returns {string} The string that was input with cleaned up handlebars statements as required.
*/
function cleanHandlebars(string) {
// TODO: handle these types of statement
// every statement must have the "all" helper added e.g.
// {{ person }} => {{ html person }}
// escaping strings must be handled as such:
// {{ person name }} => {{ [person name] }}
// {{ obj.person name }} => {{ obj.[person name] }}
let charToReplace = {
"[": ".",
"]": "",
}
let regex = new RegExp(HBS_CLEANING_REGEX)
let matches = string.match(regex)
if (matches == null) {
return string
}
for (let match of matches) {
let baseIdx = string.indexOf(match)
for (let key of Object.keys(charToReplace)) {
let idxChar = match.indexOf(key)
if (idxChar !== -1) {
string =
string.slice(baseIdx, baseIdx + idxChar) +
charToReplace[key] +
string.slice(baseIdx + idxChar + 1)
}
}
}
return string
}
/** /**
* utility function to check if the object is valid * utility function to check if the object is valid
*/ */
@ -70,7 +26,6 @@ function testObject(object) {
*/ */
module.exports.processObject = async (object, context) => { module.exports.processObject = async (object, context) => {
testObject(object) testObject(object)
// TODO: carry out any async calls before carrying out async call
for (let key of Object.keys(object)) { for (let key of Object.keys(object)) {
let val = object[key] let val = object[key]
if (typeof val === "string") { if (typeof val === "string") {

View File

@ -3,6 +3,20 @@ const {
} = require("../src/index") } = require("../src/index")
describe("Handling context properties with spaces in their name", () => { describe("Handling context properties with spaces in their name", () => {
it("should allow through literal specifiers", async () => {
const output = await processString("test {{ [test thing] }}", {
"test thing": 1
})
expect(output).toBe("test 1")
})
it("should convert to dot notation where required", async () => {
const output = await processString("test {{ test[0] }}", {
test: [2]
})
expect(output).toBe("test 2")
})
it("should be able to handle a property with a space in its name", async () => { it("should be able to handle a property with a space in its name", async () => {
const output = await processString("hello my name is {{ person name }}", { const output = await processString("hello my name is {{ person name }}", {
"person name": "Mike", "person name": "Mike",