Skip to content

Commit 4f916ea

Browse files
authored
refactor: move substitution out of source template (#241)
1 parent a3ce58a commit 4f916ea

File tree

6 files changed

+194
-102
lines changed

6 files changed

+194
-102
lines changed

dist/cli/index.js

Lines changed: 75 additions & 75 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/application/cli/create-entry-command.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ import {
1919
import { Repository } from '../../domain/repository.js';
2020
import {
2121
SourceTemplate,
22-
SubstitutableVar,
2322
UnsubstitutedVarsError,
2423
} from '../../domain/source-template.js';
2524
import { SourceTemplateError } from '../../domain/source-template.js';
2625
import { CreateEntryArgs } from './yargs.js';
26+
import { SubstitutableVar } from '../../domain/substitution.js';
2727

2828
export interface CreateEntryCommandOutput {
2929
moduleName: string;
@@ -113,12 +113,15 @@ export class CreateEntryCommand {
113113
console.error(
114114
`Source template ${e.path} has unsubstituted variables ${Array.from(e.unsubstituted).join(',')}`
115115
);
116-
if (e.unsubstituted.has('OWNER') || e.unsubstituted.has('REPO')) {
116+
if (
117+
e.unsubstituted.has(SubstitutableVar.OWNER) ||
118+
e.unsubstituted.has(SubstitutableVar.REPO)
119+
) {
117120
console.error(
118121
'Did you forget to pass --github-repository to substitute the OWNER and REPO variables?'
119122
);
120123
}
121-
if (e.unsubstituted.has('TAG')) {
124+
if (e.unsubstituted.has(SubstitutableVar.TAG)) {
122125
console.error(
123126
'Did you forget to pass --tag to substitute the TAG variable?'
124127
);

src/domain/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ts_project(
1717
"repository.ts",
1818
"ruleset-repository.ts",
1919
"source-template.ts",
20+
"substitution.ts",
2021
"user.ts",
2122
"version.ts",
2223
],
@@ -54,6 +55,7 @@ ts_project(
5455
"repository.spec.ts",
5556
"ruleset-repository.spec.ts",
5657
"source-template.spec.ts",
58+
"substitution.spec.ts",
5759
"version.spec.ts",
5860
],
5961
deps = [

src/domain/source-template.ts

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import fs from 'node:fs';
22

3+
import {
4+
getUnsubstitutedVars,
5+
SubstitutableVar,
6+
substituteVars,
7+
} from './substitution.js';
8+
39
export class SourceTemplateError extends Error {
410
constructor(
511
public readonly path: string,
@@ -18,8 +24,6 @@ export class UnsubstitutedVarsError extends Error {
1824
}
1925
}
2026

21-
export type SubstitutableVar = 'OWNER' | 'REPO' | 'TAG' | 'VERSION';
22-
2327
export class SourceTemplate {
2428
private sourceJson: Record<string, unknown>;
2529

@@ -61,7 +65,7 @@ export class SourceTemplate {
6165
for (const prop of ['url', 'strip_prefix'].filter(
6266
(prop) => prop in this.sourceJson
6367
)) {
64-
this.sourceJson[prop] = this.replaceVariables(
68+
this.sourceJson[prop] = substituteVars(
6569
this.sourceJson[prop] as string,
6670
vars
6771
);
@@ -71,38 +75,20 @@ export class SourceTemplate {
7175
}
7276

7377
public validateFullySubstituted(): void {
74-
const tempalteVars: SubstitutableVar[] = [
75-
'OWNER',
76-
'REPO',
77-
'TAG',
78-
'VERSION',
79-
];
8078
const unsubstituted = new Set<SubstitutableVar>();
8179
for (const prop of ['url', 'strip_prefix'].filter(
8280
(prop) => prop in this.sourceJson
8381
)) {
84-
for (const templateVar of tempalteVars) {
85-
if ((this.sourceJson[prop] as string).includes(`{${templateVar}}`)) {
86-
unsubstituted.add(templateVar);
87-
}
88-
}
82+
getUnsubstitutedVars(this.sourceJson[prop] as string).forEach((v) =>
83+
unsubstituted.add(v)
84+
);
8985
}
9086

9187
if (unsubstituted.size > 0) {
9288
throw new UnsubstitutedVarsError(this.filePath, unsubstituted);
9389
}
9490
}
9591

96-
private replaceVariables(
97-
str: string,
98-
vars: Partial<Record<SubstitutableVar, string>>
99-
) {
100-
for (const key of Object.keys(vars)) {
101-
str = str.replaceAll(`{${key}}`, vars[key as SubstitutableVar]);
102-
}
103-
return str;
104-
}
105-
10692
public setIntegrityHash(integrityHash: string) {
10793
this.sourceJson.integrity = integrityHash;
10894
}

src/domain/substitution.spec.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
getUnsubstitutedVars,
3+
SubstitutableVar,
4+
substituteVars,
5+
} from './substitution';
6+
7+
describe('substituteVars', () => {
8+
test('substitutes a variable', () => {
9+
const str = 'The repo is {REPO}';
10+
const sub = substituteVars(str, { REPO: 'foo' });
11+
12+
expect(sub).toEqual('The repo is foo');
13+
});
14+
15+
test('substitutes duplicates of a variable', () => {
16+
const str = 'The repo is {REPO}. Yes, it is {REPO}.';
17+
const sub = substituteVars(str, { REPO: 'foo' });
18+
19+
expect(sub).toEqual('The repo is foo. Yes, it is foo.');
20+
});
21+
22+
test('substitutes multiple variables', () => {
23+
const str = 'The repo is {REPO}. The owner is {OWNER}.';
24+
const sub = substituteVars(str, { REPO: 'foo', OWNER: 'bar' });
25+
26+
expect(sub).toEqual('The repo is foo. The owner is bar.');
27+
});
28+
29+
test('does not substitute an unknown variable', () => {
30+
const str = 'The meaning of life is {UNKNOWN}.';
31+
const sub = substituteVars(str, { REPO: 'foo' });
32+
33+
expect(sub).toEqual('The meaning of life is {UNKNOWN}.');
34+
});
35+
});
36+
37+
describe('getUnsubstitutedVars', () => {
38+
test('gets an unsubstituted var', () => {
39+
const str = 'The repo is {REPO}';
40+
const vars = getUnsubstitutedVars(str);
41+
42+
expect(vars.size).toEqual(1);
43+
expect(vars.has(SubstitutableVar.REPO)).toBe(true);
44+
});
45+
46+
test('gets duplicated unsubstituted var', () => {
47+
const str = 'The repo is {REPO}. Yes, it is {REPO}.';
48+
const vars = getUnsubstitutedVars(str);
49+
50+
expect(vars.size).toEqual(1);
51+
expect(vars.has(SubstitutableVar.REPO)).toBe(true);
52+
});
53+
54+
test('gets an multiple ubsubstituted vars', () => {
55+
const str = 'The repo is {REPO}. The owner is {OWNER}.';
56+
const vars = getUnsubstitutedVars(str);
57+
58+
expect(vars.size).toEqual(2);
59+
expect(vars.has(SubstitutableVar.REPO)).toBe(true);
60+
expect(vars.has(SubstitutableVar.OWNER)).toBe(true);
61+
});
62+
63+
test('does not get unknown vars', () => {
64+
const str = 'The meaning of life is {UNKNOWN}.';
65+
const vars = getUnsubstitutedVars(str);
66+
67+
expect(vars.size).toEqual(0);
68+
});
69+
});

src/domain/substitution.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// export type SubstitutableVar = 'OWNER' | 'REPO' | 'TAG' | 'VERSION';
2+
3+
export enum SubstitutableVar {
4+
OWNER = 'OWNER',
5+
REPO = 'REPO',
6+
TAG = 'TAG',
7+
VERSION = 'VERSION',
8+
}
9+
10+
export const ALL_SUBSTITUTABLE_VARS = Object.values(SubstitutableVar);
11+
12+
export function substituteVars(
13+
str: string,
14+
vars: Partial<Record<SubstitutableVar, string>>
15+
): string {
16+
for (const key of Object.keys(vars)) {
17+
str = str.replaceAll(`{${key}}`, vars[key as SubstitutableVar]);
18+
}
19+
return str;
20+
}
21+
22+
export function getUnsubstitutedVars(str: string): Set<SubstitutableVar> {
23+
const unsubstituted = new Set<SubstitutableVar>();
24+
25+
for (const templateVar of ALL_SUBSTITUTABLE_VARS) {
26+
if (str.includes(`{${templateVar}}`)) {
27+
unsubstituted.add(templateVar);
28+
}
29+
}
30+
31+
return unsubstituted;
32+
}

0 commit comments

Comments
 (0)