@@ -2,14 +2,23 @@ import generator from '@babel/generator';
2
2
import { parse } from '@babel/parser' ;
3
3
import traverse from '@babel/traverse' ;
4
4
import * as t from '@babel/types' ;
5
+ import YAML , { isMap , isSeq } from 'yaml' ;
5
6
6
- export type AstSet = {
7
+ export type JsSet = {
7
8
fileName : string ;
8
9
ast : t . File ;
9
10
cubeDefinition : t . ObjectExpression ;
10
11
} ;
11
12
12
- export type AstByCubeName = Record < string , AstSet > ;
13
+ export type YamlSet = {
14
+ fileName : string ;
15
+ yaml : YAML . Document ;
16
+ cubeDefinition : YAML . YAMLMap ;
17
+ } ;
18
+
19
+ const JINJA_SYNTAX = / { % | % } | { { | } } / ig;
20
+
21
+ export type AstByCubeName = Record < string , ( JsSet | YamlSet ) > ;
13
22
14
23
export interface CubeConverterInterface {
15
24
convert ( astByCubeName : AstByCubeName ) : void ;
@@ -27,47 +36,112 @@ export class CubeSchemaConverter {
27
36
28
37
public constructor ( protected fileRepository : any , protected converters : CubeConverterInterface [ ] ) { }
29
38
30
- protected async prepare ( ) : Promise < void > {
39
+ /**
40
+ * Parse Schema files from the repository and create a mapping of cube names to schema files.
41
+ * If optional `cubeName` parameter is passed - only file with asked cube is parsed and stored.
42
+ * @param cubeName
43
+ * @protected
44
+ */
45
+ protected async prepare ( cubeName ?: string ) : Promise < void > {
31
46
this . dataSchemaFiles = await this . fileRepository . dataSchemaFiles ( ) ;
32
47
33
48
this . dataSchemaFiles . forEach ( ( schemaFile ) => {
34
- const ast = this . parse ( schemaFile ) ;
35
-
36
- traverse ( ast , {
37
- CallExpression : ( path ) => {
38
- if ( t . isIdentifier ( path . node . callee ) ) {
39
- const args = path . get ( 'arguments' ) ;
40
-
41
- if ( path . node . callee . name === 'cube' ) {
42
- if ( args ?. [ args . length - 1 ] ) {
43
- let cubeName : string | undefined ;
44
-
45
- if ( args [ 0 ] . node . type === 'StringLiteral' && args [ 0 ] . node . value ) {
46
- cubeName = args [ 0 ] . node . value ;
47
- } else if ( args [ 0 ] . node . type === 'TemplateLiteral' && args [ 0 ] . node . quasis ?. [ 0 ] ?. value . cooked ) {
48
- cubeName = args [ 0 ] . node . quasis ?. [ 0 ] ?. value . cooked ;
49
- }
50
-
51
- if ( cubeName == null ) {
52
- throw new Error ( `Error parsing ${ schemaFile . fileName } ` ) ;
53
- }
54
-
55
- if ( t . isObjectExpression ( args [ 1 ] ?. node ) && ast != null ) {
56
- this . parsedFiles [ cubeName ] = {
57
- fileName : schemaFile . fileName ,
58
- ast,
59
- cubeDefinition : args [ 1 ] . node ,
60
- } ;
61
- }
49
+ if ( schemaFile . fileName . endsWith ( '.js' ) ) {
50
+ this . transformJS ( schemaFile , cubeName ) ;
51
+ } else if ( ( schemaFile . fileName . endsWith ( '.yml' ) || schemaFile . fileName . endsWith ( '.yaml' ) ) && ! schemaFile . content . match ( JINJA_SYNTAX ) ) {
52
+ // Jinja-templated data models are not supported in Rollup Designer yet, so we're ignoring them,
53
+ // and if user has chosen the cube from such file - it won't be found during generation.
54
+ this . transformYaml ( schemaFile , cubeName ) ;
55
+ }
56
+ } ) ;
57
+ }
58
+
59
+ protected transformYaml ( schemaFile : SchemaFile , filterCubeName ?: string ) {
60
+ if ( ! schemaFile . content . trim ( ) ) {
61
+ return ;
62
+ }
63
+
64
+ const yamlDoc = YAML . parseDocument ( schemaFile . content ) ;
65
+ if ( ! yamlDoc ?. contents ) {
66
+ return ;
67
+ }
68
+
69
+ const root = yamlDoc . contents ;
70
+
71
+ if ( ! isMap ( root ) ) {
72
+ return ;
73
+ }
74
+
75
+ const cubesPair = root . items . find ( ( item ) => {
76
+ const key = item . key as YAML . Scalar ;
77
+ return key ?. value === 'cubes' ;
78
+ } ) ;
79
+
80
+ if ( ! cubesPair || ! isSeq ( cubesPair . value ) ) {
81
+ return ;
82
+ }
83
+
84
+ for ( const cubeNode of cubesPair . value . items ) {
85
+ if ( isMap ( cubeNode ) ) {
86
+ const cubeNamePair = cubeNode . items . find ( ( item ) => {
87
+ const key = item . key as YAML . Scalar ;
88
+ return key ?. value === 'name' ;
89
+ } ) ;
90
+
91
+ const cubeName = ( cubeNamePair ?. value as YAML . Scalar ) . value ;
92
+
93
+ if ( cubeName && typeof cubeName === 'string' && ( ! filterCubeName || cubeName === filterCubeName ) ) {
94
+ this . parsedFiles [ cubeName ] = {
95
+ fileName : schemaFile . fileName ,
96
+ yaml : yamlDoc ,
97
+ cubeDefinition : cubeNode ,
98
+ } ;
99
+
100
+ if ( cubeName === filterCubeName ) {
101
+ return ;
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ protected transformJS ( schemaFile : SchemaFile , filterCubeName ?: string ) {
109
+ const ast = this . parseJS ( schemaFile ) ;
110
+
111
+ traverse ( ast , {
112
+ CallExpression : ( path ) => {
113
+ if ( t . isIdentifier ( path . node . callee ) ) {
114
+ const args = path . get ( 'arguments' ) ;
115
+
116
+ if ( path . node . callee . name === 'cube' ) {
117
+ if ( args ?. [ args . length - 1 ] ) {
118
+ let cubeName : string | undefined ;
119
+
120
+ if ( args [ 0 ] . node . type === 'StringLiteral' && args [ 0 ] . node . value ) {
121
+ cubeName = args [ 0 ] . node . value ;
122
+ } else if ( args [ 0 ] . node . type === 'TemplateLiteral' && args [ 0 ] . node . quasis ?. [ 0 ] ?. value . cooked ) {
123
+ cubeName = args [ 0 ] . node . quasis ?. [ 0 ] ?. value . cooked ;
124
+ }
125
+
126
+ if ( cubeName == null ) {
127
+ throw new Error ( `Error parsing ${ schemaFile . fileName } ` ) ;
128
+ }
129
+
130
+ if ( t . isObjectExpression ( args [ 1 ] ?. node ) && ast != null && ( ! filterCubeName || cubeName === filterCubeName ) ) {
131
+ this . parsedFiles [ cubeName ] = {
132
+ fileName : schemaFile . fileName ,
133
+ ast,
134
+ cubeDefinition : args [ 1 ] . node ,
135
+ } ;
62
136
}
63
137
}
64
138
}
65
- } ,
66
- } ) ;
139
+ }
140
+ } ,
67
141
} ) ;
68
142
}
69
143
70
- protected parse ( file : SchemaFile ) {
144
+ protected parseJS ( file : SchemaFile ) {
71
145
try {
72
146
return parse ( file . content , {
73
147
sourceFilename : file . fileName ,
@@ -86,19 +160,25 @@ export class CubeSchemaConverter {
86
160
}
87
161
}
88
162
89
- public async generate ( ) {
90
- await this . prepare ( ) ;
163
+ public async generate ( cubeName ?: string ) {
164
+ await this . prepare ( cubeName ) ;
91
165
92
166
this . converters . forEach ( ( converter ) => {
93
167
converter . convert ( this . parsedFiles ) ;
94
168
} ) ;
95
169
}
96
170
97
171
public getSourceFiles ( ) {
98
- return Object . entries ( this . parsedFiles ) . map ( ( [ cubeName , file ] ) => ( {
99
- cubeName,
100
- fileName : file . fileName ,
101
- source : generator ( file . ast , { } ) . code ,
102
- } ) ) ;
172
+ return Object . entries ( this . parsedFiles ) . map ( ( [ cubeName , file ] ) => {
173
+ const source = 'ast' in file
174
+ ? generator ( file . ast , { } ) . code
175
+ : String ( file . yaml ) ;
176
+
177
+ return {
178
+ cubeName,
179
+ fileName : file . fileName ,
180
+ source,
181
+ } ;
182
+ } ) ;
103
183
}
104
184
}
0 commit comments