Skip to content

Commit 9177bf9

Browse files
hanslfilipesilva
authored andcommitted
feat(@angular-devkit/schematics): allow path templates to override some options
Like the interpolation and the pipe character. The default behaviour is kept. We will need to use those to work around some limiations of the google3 file system which does not accept "@".
1 parent 8b7516d commit 9177bf9

File tree

2 files changed

+91
-34
lines changed

2 files changed

+91
-34
lines changed

packages/angular_devkit/schematics/src/rules/template.ts

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,24 @@ export class InvalidPipeException extends BaseException {
2727
}
2828

2929

30-
export const kPathTemplateComponentRE = /__(.+?)__/g;
31-
export const kPathTemplatePipeRE = /@([^@]+)/;
32-
33-
3430
export type PathTemplateValue = boolean | string | number | undefined;
3531
export type PathTemplatePipeFunction = (x: string) => PathTemplateValue;
36-
export type PathTemplateOptions = {
37-
[key: string]: PathTemplateValue | PathTemplateOptions | PathTemplatePipeFunction,
32+
export type PathTemplateData = {
33+
[key: string]: PathTemplateValue | PathTemplateData | PathTemplatePipeFunction,
3834
};
3935

4036

37+
export interface PathTemplateOptions {
38+
// Interpolation start and end strings.
39+
interpolationStart: string;
40+
// Interpolation start and end strings.
41+
interpolationEnd: string;
42+
43+
// Separator for pipes. Do not specify to remove pipe support.
44+
pipeSeparator?: string;
45+
}
46+
47+
4148
export function applyContentTemplate<T>(options: T): FileOperator {
4249
return (entry: FileEntry) => {
4350
const {path, content} = entry;
@@ -58,57 +65,91 @@ export function contentTemplate<T>(options: T): Rule {
5865
}
5966

6067

61-
export function applyPathTemplate<T extends PathTemplateOptions>(options: T): FileOperator {
68+
export function applyPathTemplate<T extends PathTemplateData>(
69+
data: T,
70+
options: PathTemplateOptions = {
71+
interpolationStart: '__',
72+
interpolationEnd: '__',
73+
pipeSeparator: '@',
74+
},
75+
): FileOperator {
76+
const is = options.interpolationStart;
77+
const ie = options.interpolationEnd;
78+
const isL = is.length;
79+
const ieL = ie.length;
80+
6281
return (entry: FileEntry) => {
63-
let path = entry.path;
82+
let path = entry.path as string;
6483
const content = entry.content;
6584
const original = path;
6685

67-
// Path template.
68-
path = normalize(path.replace(kPathTemplateComponentRE, (_, match) => {
69-
const [name, ...pipes] = match.split(kPathTemplatePipeRE);
70-
let value = options[name];
86+
let start = path.indexOf(is);
87+
// + 1 to have at least a length 1 name. `____` is not valid.
88+
let end = path.indexOf(ie, start + isL + 1);
7189

72-
if (typeof value == 'function') {
73-
value = value.call(options, original);
74-
}
90+
while (start != -1 && end != -1) {
91+
const match = path.substring(start + isL, end);
92+
let replacement = data[match];
7593

76-
if (value === undefined) {
77-
throw new OptionIsNotDefinedException(name);
78-
}
94+
if (!options.pipeSeparator) {
95+
if (typeof replacement == 'function') {
96+
replacement = replacement.call(data, original);
97+
}
7998

80-
return pipes.reduce((acc: string, pipe: string) => {
81-
if (!pipe) {
82-
return acc;
99+
if (replacement === undefined) {
100+
throw new OptionIsNotDefinedException(match);
83101
}
84-
if (!(pipe in options)) {
85-
throw new UnknownPipeException(pipe);
102+
} else {
103+
const [name, ...pipes] = match.split(options.pipeSeparator);
104+
replacement = data[name];
105+
106+
if (typeof replacement == 'function') {
107+
replacement = replacement.call(data, original);
86108
}
87-
if (typeof options[pipe] != 'function') {
88-
throw new InvalidPipeException(pipe);
109+
110+
if (replacement === undefined) {
111+
throw new OptionIsNotDefinedException(name);
89112
}
90113

91-
// Coerce to string.
92-
return '' + (options[pipe] as PathTemplatePipeFunction)(acc);
93-
}, '' + value);
94-
}));
114+
replacement = pipes.reduce((acc: string, pipe: string) => {
115+
if (!pipe) {
116+
return acc;
117+
}
118+
if (!(pipe in data)) {
119+
throw new UnknownPipeException(pipe);
120+
}
121+
if (typeof data[pipe] != 'function') {
122+
throw new InvalidPipeException(pipe);
123+
}
124+
125+
// Coerce to string.
126+
return '' + (data[pipe] as PathTemplatePipeFunction)(acc);
127+
}, '' + replacement);
128+
}
129+
130+
path = path.substring(0, start) + replacement + path.substring(end + ieL);
131+
132+
start = path.indexOf(options.interpolationStart);
133+
// See above.
134+
end = path.indexOf(options.interpolationEnd, start + isL + 1);
135+
}
95136

96-
return { path, content };
137+
return { path: normalize(path), content };
97138
};
98139
}
99140

100141

101-
export function pathTemplate<T extends PathTemplateOptions>(options: T): Rule {
142+
export function pathTemplate<T extends PathTemplateData>(options: T): Rule {
102143
return forEach(applyPathTemplate(options));
103144
}
104145

105146

106147
export function template<T>(options: T): Rule {
107148
return chain([
108149
contentTemplate(options),
109-
// Force cast to PathTemplateOptions. We need the type for the actual pathTemplate() call,
150+
// Force cast to PathTemplateData. We need the type for the actual pathTemplate() call,
110151
// but in this case we cannot do anything as contentTemplate are more permissive.
111152
// Since values are coerced to strings in PathTemplates it will be fine in the end.
112-
pathTemplate(options as {} as PathTemplateOptions),
153+
pathTemplate(options as {} as PathTemplateData),
113154
]);
114155
}

packages/angular_devkit/schematics/src/rules/template_spec.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,26 @@ describe('applyPathTemplate', () => {
5353
expect(_applyPathTemplate('/a_b_c/d__e_f__g', { e_f: 1 })).toBe('/a_b_c/d1g');
5454
});
5555

56+
it('works with complex _________...', () => {
57+
expect(_applyPathTemplate(
58+
'/_____' + 'a' + '___a__' + '_/' + '__a___/__b___',
59+
{ _a: 0, a: 1, b: 2, _: '.' },
60+
)).toBe('/.a0_/1_/2_');
61+
62+
expect(_applyPathTemplate(
63+
'_____' + '_____' + '_____' + '___',
64+
{ _: '.' },
65+
)).toBe('...___');
66+
});
67+
5668
it('works with functions', () => {
5769
let arg = '';
5870
expect(_applyPathTemplate('/a__c__b', {
59-
c: (x: string) => (arg = x, 'hello'),
71+
c: (x: string) => {
72+
arg = x;
73+
74+
return 'hello';
75+
},
6076
})).toBe('/ahellob');
6177
expect(arg).toBe('/a__c__b');
6278
});

0 commit comments

Comments
 (0)