diff --git a/packages/common/src/constants/regexp.ts b/packages/common/src/constants/regexp.ts index 95d19d708..02c9375c8 100644 --- a/packages/common/src/constants/regexp.ts +++ b/packages/common/src/constants/regexp.ts @@ -6,7 +6,8 @@ export const SLOT_ANNOTATION_SIMPLE_REGEX = /{([^ .[\]{}]+?)}/g; export const IS_VARIABLE_REGEXP = /^{.*}$/; -export const READABLE_VARIABLE_REGEXP = /{(\w{1,64})}/g; +// export const READABLE_VARIABLE_REGEXP = /{(\w{1,64})}/g; +export const READABLE_VARIABLE_REGEXP = /{(\w{1,64})((?:\.\w{1,64}|\[\d+])*)}/gi; export const VALID_CHARACTER = 'a-zA-Z'; diff --git a/packages/common/src/utils/variables.ts b/packages/common/src/utils/variables.ts index 58d737b5e..829c10ae6 100644 --- a/packages/common/src/utils/variables.ts +++ b/packages/common/src/utils/variables.ts @@ -3,14 +3,37 @@ import { READABLE_VARIABLE_REGEXP } from '@common/constants'; export const variableReplacer = ( match: string, inner: string, + selectors: string[], variables: Record, modifier?: (variable: unknown) => unknown ): unknown => { - if (inner in variables) { - return typeof modifier === 'function' ? modifier(variables[inner]) : variables[inner]; + if (!(inner in variables)) { + return match; } - return match; + let replaced: any = variables[inner]; + + let selectorString = selectors[0]; + while (selectorString.length > 0) { + // eslint-disable-next-line no-loop-func + selectorString = selectorString.replace(/^\.(\w{1,64})/, (_m, field) => { + replaced = replaced[field]; + return ''; + }); + if (replaced === undefined) { + break; + } + // eslint-disable-next-line no-loop-func + selectorString = selectorString.replace(/^\[(\d+)]/, (_m, index) => { + replaced = replaced[index]; + return ''; + }); + if (replaced === undefined) { + break; + } + } + + return typeof modifier === 'function' ? modifier(replaced) : replaced; }; export const replaceVariables = ( @@ -23,7 +46,9 @@ export const replaceVariables = ( return ''; } - return phrase.replace(READABLE_VARIABLE_REGEXP, (match, inner) => String(variableReplacer(match, inner, variables, modifier))); + return phrase.replace(READABLE_VARIABLE_REGEXP, (match, inner, ...selectors) => + String(variableReplacer(match, inner, selectors, variables, modifier)) + ); }; // turn float variables to 2 decimal places diff --git a/packages/common/tests/utils/variables.unit.ts b/packages/common/tests/utils/variables.unit.ts new file mode 100644 index 000000000..d23123b70 --- /dev/null +++ b/packages/common/tests/utils/variables.unit.ts @@ -0,0 +1,50 @@ +import { replaceVariables } from '@common/utils/variables'; +import { expect } from 'chai'; + +describe('Utils | variables', () => { + describe('replaceVariables', () => { + const variables = { + name: 'bob', + age: 32, + favFoods: ['takoyaki', 'onigiri', 'taiyaki'], + job: { + company: 'voiceflow', + position: 'software engineer', + team: 'creator', + }, + }; + it('correctly replaces simple variables', () => { + expect(replaceVariables('hello, my name is {name}, and i am {age} years old', variables)).to.eq('hello, my name is bob, and i am 32 years old'); + expect(replaceVariables('{name} {name} {name}', variables)).to.eq('bob bob bob'); + }); + it('variables that are not defined do not get expanded', () => { + expect(replaceVariables('hello, my name is {name} and i work at {workplace}', variables)).to.eq( + 'hello, my name is bob and i work at {workplace}' + ); + expect(replaceVariables('hello, my name is {Name}', variables)).to.eq('hello, my name is {Name}'); + }); + + it('array access works', () => { + expect(replaceVariables('most favorite food is {favFoods[0]}, second favorite is {favFoods[1]}, and third is {favFoods[2]}', variables)).to.eq( + 'most favorite food is takoyaki, second favorite is onigiri, and third is taiyaki' + ); + }); + // it('index out of range', () => {}); + + it('object access works', () => { + expect(replaceVariables('i work at {job.company} as a {job.position} on the {job.team} team', variables)).to.eq( + 'i work at voiceflow as a software engineer on the creator team' + ); + }); + // it('non-existent fields', () => {}); + + it('weird cases', () => { + const variables = { + '': 6969, + var: '{name}', + }; + expect(replaceVariables('this is a blank variable {}', variables)).to.eq('this is a blank variable {}'); + expect(replaceVariables('{var}', variables)).to.eq('{name}'); + }); + }); +});