mirror of
https://github.com/usebruno/bruno.git
synced 2025-02-07 13:30:19 +01:00
feat: add recursive interpolate (#2234)
* feat: add recursive interpolate fixes #2227 * test(bruno-common): fix test with 3 level of recursion * fix(bruno-common): add ability to reference the same variable repeatedly
This commit is contained in:
parent
ec1b734b3a
commit
271f988d99
@ -6,6 +6,11 @@
|
||||
* LICENSE file at https://github.com/graphql/codemirror-graphql/tree/v0.8.3
|
||||
*/
|
||||
|
||||
// Todo: Fix this
|
||||
// import { interpolate } from '@usebruno/common';
|
||||
import brunoCommon from '@usebruno/common';
|
||||
const { interpolate } = brunoCommon;
|
||||
|
||||
let CodeMirror;
|
||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
const { get } = require('lodash');
|
||||
@ -21,7 +26,7 @@ if (!SERVER_RENDERED) {
|
||||
// str is of format {{variableName}}, extract variableName
|
||||
// we are seeing that from the gql query editor, the token string is of format variableName
|
||||
const variableName = str.replace('{{', '').replace('}}', '').trim();
|
||||
const variableValue = get(options.variables, variableName);
|
||||
const variableValue = interpolate(get(options.variables, variableName), options.variables);
|
||||
|
||||
const into = document.createElement('div');
|
||||
const descriptionDiv = document.createElement('div');
|
||||
|
@ -13,6 +13,7 @@
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "rollup -c",
|
||||
"prepack": "npm run test && npm run build"
|
||||
|
@ -169,3 +169,170 @@ describe('interpolate - value edge cases', () => {
|
||||
expect(result).toBe(inputString);
|
||||
});
|
||||
});
|
||||
|
||||
describe('interpolate - recursive', () => {
|
||||
it('should replace placeholders with 1 level of recursion with values from the object', () => {
|
||||
const inputString = '{{user.message}}';
|
||||
const inputObject = {
|
||||
'user.message': 'Hello, my name is {{user.name}} and I am {{user.age}} years old',
|
||||
'user.name': 'Bruno',
|
||||
user: {
|
||||
age: 4
|
||||
}
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe('Hello, my name is Bruno and I am 4 years old');
|
||||
});
|
||||
|
||||
it('should replace placeholders with 2 level of recursion with values from the object', () => {
|
||||
const inputString = '{{user.message}}';
|
||||
const inputObject = {
|
||||
'user.message': 'Hello, my name is {{user.name}} and I am {{user.age}} years old',
|
||||
'user.name': 'Bruno {{user.lastName}}',
|
||||
'user.lastName': 'Dog',
|
||||
user: {
|
||||
age: 4
|
||||
}
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe('Hello, my name is Bruno Dog and I am 4 years old');
|
||||
});
|
||||
|
||||
it('should replace placeholders with 3 level of recursion with values from the object', () => {
|
||||
const inputString = '{{user.message}}';
|
||||
const inputObject = {
|
||||
'user.message': 'Hello, my name is {{user.full_name}} and I am {{user.age}} years old',
|
||||
'user.full_name': '{{user.name}}',
|
||||
'user.name': 'Bruno {{user.lastName}}',
|
||||
'user.lastName': 'Dog',
|
||||
user: {
|
||||
age: 4
|
||||
}
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe('Hello, my name is Bruno Dog and I am 4 years old');
|
||||
});
|
||||
|
||||
it('should handle missing values with 1 level of recursion by leaving the placeholders unchanged using {{}} as delimiters', () => {
|
||||
const inputString = '{{user.message}}';
|
||||
const inputObject = {
|
||||
'user.message': 'Hello, my name is {{user.name}} and I am {{user.age}} years old',
|
||||
user: {
|
||||
age: 4
|
||||
}
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe('Hello, my name is {{user.name}} and I am 4 years old');
|
||||
});
|
||||
|
||||
it('should handle all valid keys with 1 level of recursion', () => {
|
||||
const message = `
|
||||
Hi, I am {{user.full_name}},
|
||||
I am {{user.age}} years old.
|
||||
My favorite food is {{user.fav-food[0]}} and {{user.fav-food[1]}}.
|
||||
I like attention: {{user.want.attention}}
|
||||
`;
|
||||
const inputObject = {
|
||||
user: {
|
||||
message,
|
||||
full_name: 'Bruno',
|
||||
age: 4,
|
||||
'fav-food': ['egg', 'meat'],
|
||||
'want.attention': true
|
||||
}
|
||||
};
|
||||
|
||||
const inputStr = '{{user.message}}';
|
||||
const expectedStr = `
|
||||
Hi, I am Bruno,
|
||||
I am 4 years old.
|
||||
My favorite food is egg and meat.
|
||||
I like attention: true
|
||||
`;
|
||||
const result = interpolate(inputStr, inputObject);
|
||||
expect(result).toBe(expectedStr);
|
||||
});
|
||||
|
||||
it('should not process 1 level of cycle recursion with values from the object', () => {
|
||||
const inputString = '{{recursion}}';
|
||||
const inputObject = {
|
||||
recursion: '{{recursion}}'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe('{{recursion}}');
|
||||
});
|
||||
|
||||
it('should not process 2 level of cycle recursion with values from the object', () => {
|
||||
const inputString = '{{recursion}}';
|
||||
const inputObject = {
|
||||
recursion: '{{recursion2}}',
|
||||
recursion2: '{{recursion}}'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe('{{recursion2}}');
|
||||
});
|
||||
|
||||
it('should not process 3 level of cycle recursion with values from the object', () => {
|
||||
const inputString = '{{recursion}}';
|
||||
const inputObject = {
|
||||
recursion: '{{recursion2}}',
|
||||
recursion2: '{{recursion3}}',
|
||||
recursion3: '{{recursion}}'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe('{{recursion2}}');
|
||||
});
|
||||
|
||||
it('should replace repetead placeholders with 1 level of recursion with values from the object', () => {
|
||||
const inputString = '{{repetead}}';
|
||||
const inputObject = {
|
||||
repetead: '{{repetead2}} {{repetead2}}',
|
||||
repetead2: 'repetead2'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe(new Array(2).fill('repetead2').join(' '));
|
||||
});
|
||||
|
||||
it('should replace repetead placeholders with 2 level of recursion with values from the object', () => {
|
||||
const inputString = '{{repetead}}';
|
||||
const inputObject = {
|
||||
repetead: '{{repetead2}} {{repetead2}}',
|
||||
repetead2: '{{repetead3}} {{repetead3}} {{repetead3}}',
|
||||
repetead3: 'repetead3'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe(new Array(6).fill('repetead3').join(' '));
|
||||
});
|
||||
|
||||
it('should replace repetead placeholders with 3 level of recursion with values from the object', () => {
|
||||
const inputString = '{{repetead}}';
|
||||
const inputObject = {
|
||||
repetead: '{{repetead2}} {{repetead2}}',
|
||||
repetead2: '{{repetead3}} {{repetead3}} {{repetead3}}',
|
||||
repetead3: '{{repetead4}} {{repetead4}} {{repetead4}} {{repetead4}}',
|
||||
repetead4: 'repetead4'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
expect(result).toBe(new Array(24).fill('repetead4').join(' '));
|
||||
});
|
||||
});
|
||||
|
@ -11,6 +11,7 @@
|
||||
* Output: Hello, my name is Bruno and I am 4 years old
|
||||
*/
|
||||
|
||||
import { Set } from 'typescript';
|
||||
import { flattenObject } from '../utils';
|
||||
|
||||
const interpolate = (str: string, obj: Record<string, any>): string => {
|
||||
@ -18,14 +19,40 @@ const interpolate = (str: string, obj: Record<string, any>): string => {
|
||||
return str;
|
||||
}
|
||||
|
||||
const patternRegex = /\{\{([^}]+)\}\}/g;
|
||||
const flattenedObj = flattenObject(obj);
|
||||
const result = str.replace(patternRegex, (match, placeholder) => {
|
||||
const replacement = flattenedObj[placeholder];
|
||||
return replacement !== undefined ? replacement : match;
|
||||
});
|
||||
|
||||
return result;
|
||||
return replace(str, flattenedObj);
|
||||
};
|
||||
|
||||
const replace = (
|
||||
str: string,
|
||||
flattenedObj: Record<string, any>,
|
||||
visited = new Set<String>(),
|
||||
results = new Map<string, string>()
|
||||
): string => {
|
||||
const patternRegex = /\{\{([^}]+)\}\}/g;
|
||||
|
||||
return str.replace(patternRegex, (match, placeholder) => {
|
||||
const replacement = flattenedObj[placeholder];
|
||||
|
||||
if (results.has(match)) {
|
||||
return results.get(match);
|
||||
}
|
||||
|
||||
if (patternRegex.test(replacement) && !visited.has(match)) {
|
||||
visited.add(match);
|
||||
const result = replace(replacement, flattenedObj, visited, results);
|
||||
results.set(match, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
visited.add(match);
|
||||
const result = replacement !== undefined ? replacement : match;
|
||||
results.set(match, result);
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
export default interpolate;
|
||||
|
Loading…
Reference in New Issue
Block a user