Skip to content

Commit

Permalink
Make variable names case-insensitive (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
qetza committed Mar 12, 2024
1 parent 98fc023 commit 9c7015f
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 69 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog

## v1.3.0
- Make variable names case-insensitive

## v1.2.0
- Replace `merge` with `flattenAndMerge` to flatten objects and merge

Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -55,7 +55,7 @@ Example: `**/*.json; !local/ => out/*.json` will match all files ending with `.j

`--variables <list>`

A list of JSON encoded key/values.
A list of JSON encoded key/values (keys are **case-insensitive**).

If an entry starts with:
- `@`: the value will be interpreted as a path to a JSON file
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "@qetza/replacetokens",
"version": "1.2.0",
"version": "1.3.0",
"description": "replace tokens in files",
"author": "Guillaume ROUCHON",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion src/bin/run.ts
Expand Up @@ -14,7 +14,7 @@ export async function run() {
// parse arguments
var argv = await yargs(process.argv.slice(2))
.scriptName('replacetokens')
.version('1.2.0')
.version('1.3.0')
.usage('$0 [args]')
.help()
.options({
Expand Down
47 changes: 29 additions & 18 deletions src/index.ts
Expand Up @@ -66,21 +66,29 @@ export class Counter {
transforms: number = 0;
}

export function flattenAndMerge(separator: string, ...objects: { [key: string]: any }[]): { [key: string]: any } {
export function flattenAndMerge(separator: string, ...objects: { [key: string]: any }[]): { [key: string]: string } {
return objects.reduce((result, current) => {
return { ...result, ...flatten(current, separator) };
const values = {};
for (const [key, value] of Object.entries(flatten(current, separator))) {
values[key] = value.value;
}

return { ...result, ...values };
}, {});
}
function flatten(object: Object, separator: string, parentKey?: string): { [key: string]: string } {
function flatten(
object: Object,
separator: string,
parentKey?: string
): { [key: string]: { name: string; value: string } } {
let result = {};

Object.keys(object).forEach((key: string) => {
const value = object[key];
const flattenKey = parentKey ? `${parentKey}${separator}${key}` : key;
for (const [key, value] of Object.entries(object)) {
let flattenKey = parentKey ? `${parentKey}${separator}${key}` : key;

if (value && typeof value === 'object') result = { ...result, ...flatten(value, separator, flattenKey) };
else result[flattenKey] = value?.toString() ?? '';
});
else result[flattenKey.toUpperCase()] = { name: flattenKey, value: value?.toString() ?? '' };
}

return result;
}
Expand Down Expand Up @@ -256,19 +264,21 @@ function loadVariables(variables: { [key: string]: any }, options: Options): { [
console.group('loading variables');

try {
// parse, flatten and stringify json variables
// flatten with uppercase and stringify json variables
const data = flatten(variables ?? {}, options.separator!);

// log variables
let count = 0;
for (const key of Object.keys(data)) {
++count;
console.debug(`loaded '${key}'`);
// get variables with case-insensitive key and value
const vars = {};
for (const [key, value] of Object.entries(data)) {
vars[key] = value.value;

console.debug(`loaded '${value.name}'`);
}

const count = Object.keys(vars).length;
console.info(`${count} variable${count > 1 ? 's' : ''} loaded`);

return data;
return vars;
} finally {
console.groupEnd();
}
Expand Down Expand Up @@ -461,10 +471,11 @@ function replaceTokensInString(
}

// check recursion
if (options.recursive && names.includes(name)) throw new Error(`found cycle with token '${name}'`);
const key = name.toUpperCase();
if (options.recursive && names.includes(key)) throw new Error(`found cycle with token '${name}'`);

// replace token
let value: string = variables[name];
let value: string = variables[key];

if (value === undefined) {
// variable not found
Expand Down Expand Up @@ -515,7 +526,7 @@ function replaceTokensInString(
transformRegex,
customEscapeRegex,
options,
names.concat(name)
names.concat(key)
);
value = result.content;

Expand Down
38 changes: 19 additions & 19 deletions tests/flattenAndMerge.test.ts
Expand Up @@ -10,7 +10,7 @@ describe('flattenAndMerge', () => {

// assert
expect(result).not.toBe(source);
expect(result).toEqual(source);
expect(result).toEqual({ STRING: 'hello' });
});

it('multiple objects', async () => {
Expand All @@ -22,7 +22,7 @@ describe('flattenAndMerge', () => {
const result = flattenAndMerge('.', source1, source2);

// assert
expect(result).toEqual({ msg: 'hello world!', private: 'true', count: '2' });
expect(result).toEqual({ MSG: 'hello world!', PRIVATE: 'true', COUNT: '2' });
});

it('objects with array', async () => {
Expand All @@ -35,10 +35,10 @@ describe('flattenAndMerge', () => {

// assert
expect(result).toEqual({
'msgs.0': 'hello',
'msgs.1': 'world!',
private: 'true',
count: '2'
'MSGS.0': 'hello',
'MSGS.1': 'world!',
PRIVATE: 'true',
COUNT: '2'
});
});

Expand All @@ -50,8 +50,8 @@ describe('flattenAndMerge', () => {
obj: { scalar: true, obj: { scalar: 1.2, array: ['hello', { value: 'world!' }], scalar2: 'a' } }
};
const source2 = {
array: [{ scalar: 'hello' }],
obj: { scalar2: false, obj: { scalar: '1.3', array: ['hello world!'] } },
ARRAY: [{ scalar: 'hello' }],
obj: { scalar2: false, obj: { SCALAR: '1.3', array: ['hello world!'] } },
count: 2
};
const source3 = [
Expand All @@ -66,18 +66,18 @@ describe('flattenAndMerge', () => {

// assert
expect(result).toEqual({
scalar: 'string',
'array.0.scalar': 'hello',
'array.1.scalar': 'world!',
'obj.scalar': 'true',
'obj.scalar2': 'false',
'obj.obj.scalar': '1.3',
'obj.obj.array.0': 'hello world!',
'obj.obj.array.1.value': 'world!',
'obj.obj.scalar2': 'a',
count: '2',
SCALAR: 'string',
'ARRAY.0.SCALAR': 'hello',
'ARRAY.1.SCALAR': 'world!',
'OBJ.SCALAR': 'true',
'OBJ.SCALAR2': 'false',
'OBJ.OBJ.SCALAR': '1.3',
'OBJ.OBJ.ARRAY.0': 'hello world!',
'OBJ.OBJ.ARRAY.1.VALUE': 'world!',
'OBJ.OBJ.SCALAR2': 'a',
COUNT: '2',
'0': 'a',
'1.scalar': '2'
'1.SCALAR': '2'
});
});
});
45 changes: 43 additions & 2 deletions tests/replacetokens.test.ts
Expand Up @@ -214,6 +214,47 @@ describe('replaceTokens', () => {
});
});

describe('variables', () => {
it('logs', async () => {
// arrange
const input = await copyData('default.json', 'default1.json');
const consoleSpies = spyOnConsole();

// act
const result = await replaceTokens(normalizeSources(input), {
var1: 'var1_value',
var2: 'var2_value',
VAR3: ['var3_value0', 'var3_value1']
});

// assert
expect(consoleSpies.group).toHaveBeenCalledWith('loading variables');
expect(consoleSpies.debug).toHaveBeenCalledWith("loaded 'var1'");
expect(consoleSpies.debug).toHaveBeenCalledWith("loaded 'var2'");
expect(consoleSpies.debug).toHaveBeenCalledWith("loaded 'VAR3.0'");
expect(consoleSpies.debug).toHaveBeenCalledWith("loaded 'VAR3.1'");
expect(consoleSpies.info).toHaveBeenCalledWith('4 variables loaded');
expect(consoleSpies.groupEnd).toHaveBeenCalled();
});

it('case insensitive', async () => {
// arrange
const input = await copyData('default.separator.json', 'default1.json');
spyOnConsole();

// act
const result = await replaceTokens(
normalizeSources(input),
{ VARS: [{ value: 'var1_value' }, { VALUE: 'var2_value' }] },
{ separator: ':' }
);

// assert
expectCountersToEqual(result, 0, 1, 2, 2, 0);
await expectFilesToEqual(input, 'default.expected.json');
});
});

describe('token', () => {
it('default pattern', async () => {
// arrange
Expand Down Expand Up @@ -622,7 +663,7 @@ describe('replaceTokens', () => {
// act
const result = await replaceTokens(
normalizeSources(input),
{ var1: 'var1#{var3}#', var2: 'var2_value', var3: '_#{var4}#', var4: 'value' },
{ VAR1: 'var1#{var3}#', var2: 'var2_value', var3: '_#{var4}#', VAR4: 'value' },
{ recursive: true }
);

Expand All @@ -640,7 +681,7 @@ describe('replaceTokens', () => {
await expect(
replaceTokens(
normalizeSources(input),
{ var1: 'var1#{var2}#', var2: '_#{var1}#', var3: 'value' },
{ VAR1: 'var1#{var2}#', var2: '_#{var1}#', var3: 'value' },
{ recursive: true }
)
).rejects.toThrow("found cycle with token 'var1'");
Expand Down

0 comments on commit 9c7015f

Please sign in to comment.