@@ -27,17 +27,24 @@ export class InvalidPipeException extends BaseException {
2727}
2828
2929
30- export const kPathTemplateComponentRE = / _ _ ( .+ ?) _ _ / g;
31- export const kPathTemplatePipeRE = / @ ( [ ^ @ ] + ) / ;
32-
33-
3430export type PathTemplateValue = boolean | string | number | undefined ;
3531export 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+
4148export 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
106147export 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}
0 commit comments