Skip to content

Commit

Permalink
Add case-insensitive paths support (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
qetza committed Apr 19, 2024
1 parent 0e99705 commit 21f43e2
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog

## v1.6.0
- Add support for case-insensitive glob pattern matching ([#13](https://github.com/qetza/replacetokens/issues/13)).
- Remove `readTextFile` function.

## v1.5.0
- Externalize variable retrieval through callback to support more usage scenarios like external name normalization.
- Rename `parseVariables` to `loadVariables` and add logs.
Expand Down
12 changes: 10 additions & 2 deletions README.md
Expand Up @@ -17,6 +17,7 @@ npm install -g replacetokens
replacetokens --sources
--variables
[--add-bom]
[--case-insensitive-paths]
[--chars-to-escape]
[--encoding]
[--escape {auto, off, json, xml, custom}]
Expand Down Expand Up @@ -73,6 +74,10 @@ Example: `'@**/*.(json|yaml);!vars.local.json' '$VARS' '{ "var1": "inline", "var

Add BOM when writing files.

`--case-insensitive-paths`

Enable case-insensitive file path matching in glob patterns (sources and variables).

`--chars-to-escape <string>`

The characters to escape when using `custom` escape.
Expand Down Expand Up @@ -270,8 +275,9 @@ Load variables from the given list of strings; keys are flatten, merged are retu
See CLI documentation for the parsing pattern and constraints.

options:
- `dot`: allow patterns to match entries starting with a dot (`.`)
- `normalizeWin32`: replace back-slashes (`\`) with forward-slashes (`/`) in file paths
- `caseInsensitive` _(default: false)_: enable case-insensitive matching in file paths
- `dot` _(default: false)_: allow patterns to match entries starting with a dot (`.`)
- `normalizeWin32` _(default: false)_: replace back-slashes (`\`) with forward-slashes (`/`) in file paths
- `root`: _(default: current working directory)_: root path used when reading files with relative paths
- `separator` _(default: .)_: the separator used when flattening the keys

Expand Down Expand Up @@ -307,6 +313,8 @@ options:
- `log` _(default: warn)_: the key not found message log level
- `recursive` _(default: false)_: specifies if recursive replacement is enabled
- `root` _(default: current working directory)_: root path used when reading files with relative paths
- `sources`: specifies glob pattern options
- `caseInsensitive` _(default: false)_: enable case-insensitive matching in file paths
- `token`: specifies the token pattern
- `pattern` _(default: default)_: the token pattern
- `prefix` _(default: null)_: the token prefix if `pattern` is `custom`
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "@qetza/replacetokens",
"version": "1.5.0",
"version": "1.6.0",
"description": "replace tokens in files",
"author": "Guillaume ROUCHON",
"license": "MIT",
Expand Down
12 changes: 10 additions & 2 deletions src/bin/run.ts
Expand Up @@ -13,7 +13,7 @@ export async function run() {
// parse arguments
var argv = await yargs(process.argv.slice(2))
.scriptName('replacetokens')
.version('1.5.0')
.version('1.6.0')
.usage('$0 [args]')
.help()
.options({
Expand All @@ -30,6 +30,10 @@ export async function run() {
description: 'variables values as JSON'
},
'add-bom': { type: 'boolean', description: 'add BOM when writing files' },
'case-insensitive-paths': {
type: 'boolean',
description: 'enable case-insensitive file path matching in glob patterns (sources and variables)'
},
'chars-to-escape': { type: 'string', description: 'custom characters to escape' },
encoding: {
default: rt.Encodings.Auto,
Expand Down Expand Up @@ -191,7 +195,8 @@ export async function run() {
const variables = await rt.loadVariables(argv.variables, {
separator: argv.separator,
normalizeWin32: false,
root: argv.root
root: argv.root,
caseInsensitive: argv['case-insensitive-paths']
});
const result = await rt.replaceTokens(argv.sources, (name: string) => variables[name], {
root: argv.root,
Expand Down Expand Up @@ -220,6 +225,9 @@ export async function run() {
enabled: argv.transforms,
prefix: argv['transforms-prefix'],
suffix: argv['transforms-suffix']
},
sources: {
caseInsensitive: argv['case-insensitive-paths']
}
});

Expand Down
10 changes: 8 additions & 2 deletions src/index.ts
Expand Up @@ -57,6 +57,7 @@ export interface Options {
readonly addBOM?: boolean;
readonly escape?: { readonly type?: string; readonly chars?: string; readonly escapeChar?: string };
readonly transforms?: { readonly enabled?: boolean; readonly prefix?: string; readonly suffix?: string };
readonly sources?: { readonly caseInsensitive?: boolean };
}

export class Counter {
Expand All @@ -72,6 +73,7 @@ export interface LoadVariablesOptions {
normalizeWin32?: boolean;
root?: string;
dot?: boolean;
caseInsensitive?: boolean;
}

export async function loadVariables(
Expand Down Expand Up @@ -129,6 +131,7 @@ async function loadVariablesFromFile(name: string, options?: LoadVariablesOption
name.split(';').map(v => v.trim()),
{
absolute: true,
caseSensitiveMatch: !(options?.caseInsensitive ?? false),
cwd: options?.root,
dot: options?.dot,
onlyFiles: true,
Expand Down Expand Up @@ -181,8 +184,7 @@ function flatten(

return result;
}

export async function readTextFile(
async function readTextFile(
path: string,
encoding: string = Encodings.Auto
): Promise<{ encoding: string; content: string }> {
Expand Down Expand Up @@ -234,6 +236,9 @@ export async function replaceTokens(
log: options?.missing?.log ?? MissingVariables.Log.Warn
},
recursive: options?.recursive ?? false,
sources: {
caseInsensitive: options?.sources?.caseInsensitive ?? false
},
token: {
pattern: options?.token?.pattern ?? TokenPatterns.Default,
prefix: (() => {
Expand Down Expand Up @@ -302,6 +307,7 @@ export async function replaceTokens(
for (const pattern of patterns) {
var inputs = await fg.glob(pattern.inputPatterns, {
absolute: true,
caseSensitiveMatch: !options.sources!.caseInsensitive,
cwd: options.root,
onlyFiles: true,
unique: true
Expand Down
6 changes: 6 additions & 0 deletions tests/data/loadvariables/VARS2.json
@@ -0,0 +1,6 @@
{
"var1": "value1",
"var2": {
"sub2": ["value2"]
}
}
10 changes: 10 additions & 0 deletions tests/loadvariables.test.ts
Expand Up @@ -234,4 +234,14 @@ describe('loadVariables', () => {

expect(result).toEqual({ VAR1: 'value1', VAR2: 'file', 'VAR2.SUB2.0': 'value2', VAR3: 'file' });
});

it('options: caseInsensitive', async () => {
// act
const result = await loadVariables(['@**/vars2.json'], { root: data, caseInsensitive: true });

// assert
expect(consoleSpies.debug).toHaveBeenCalledWith(`loading from file '${path.join(data, 'VARS2.json')}'`);

expect(result).toEqual({ VAR1: 'value1', 'VAR2.SUB2.0': 'value2' });
});
});
19 changes: 19 additions & 0 deletions tests/replacetokens.test.ts
Expand Up @@ -257,6 +257,25 @@ describe('replaceTokens', () => {
await expectFileToEqual(input, 'default.json');
await expectFileToEqual(path.join(tmp, 'output/default2.json'), 'default.expected.json');
});

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

// act
const result = await replaceTokens(
normalizeSources(path.join(tmp, '**/default1.json')),
getVariableCallback({ var1: 'var1_value', var2: 'var2_value' }),
{
sources: { caseInsensitive: true }
}
);

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

describe('variables', () => {
Expand Down
30 changes: 30 additions & 0 deletions tests/run.test.ts
Expand Up @@ -254,6 +254,13 @@ describe('run', () => {
await run();

// assert
expect(loadVariablesSpy).toHaveBeenCalledWith(['{}'], {
separator: rt.Defaults.Separator,
normalizeWin32: false,
root: undefined,
caseInsensitive: undefined
});

expect(replaceTokensSpy).toHaveBeenCalledWith(['file1'], expect.any(Function), {
root: undefined,
encoding: 'auto',
Expand All @@ -278,6 +285,9 @@ describe('run', () => {
enabled: undefined,
prefix: '(',
suffix: ')'
},
sources: {
caseInsensitive: undefined
}
});

Expand Down Expand Up @@ -732,6 +742,26 @@ describe('run', () => {
);
});

it('case-insensitive-paths', async () => {
// arrange
jest.replaceProperty(process, 'argv', argv('--case-insensitive-paths'));

// act
await run();

// assert
expect(loadVariablesSpy).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ caseInsensitive: true })
);

expect(replaceTokensSpy).toHaveBeenCalledWith(
expect.anything(),
expect.any(Function),
expect.objectContaining({ sources: expect.objectContaining({ caseInsensitive: true }) })
);
});

it('replace tokens', async () => {
// arrange
await fs.mkdir(tmp, { recursive: true });
Expand Down

0 comments on commit 21f43e2

Please sign in to comment.