@@ -2,14 +2,23 @@ import generator from '@babel/generator';
22import { parse } from '@babel/parser' ;
33import traverse from '@babel/traverse' ;
44import * as t from '@babel/types' ;
5+ import YAML , { isMap , isSeq } from 'yaml' ;
56
6- export type AstSet = {
7+ export type JsSet = {
78 fileName : string ;
89 ast : t . File ;
910 cubeDefinition : t . ObjectExpression ;
1011} ;
1112
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 ) > ;
1322
1423export interface CubeConverterInterface {
1524 convert ( astByCubeName : AstByCubeName ) : void ;
@@ -27,47 +36,112 @@ export class CubeSchemaConverter {
2736
2837 public constructor ( protected fileRepository : any , protected converters : CubeConverterInterface [ ] ) { }
2938
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 > {
3146 this . dataSchemaFiles = await this . fileRepository . dataSchemaFiles ( ) ;
3247
3348 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+ } ;
62136 }
63137 }
64138 }
65- } ,
66- } ) ;
139+ }
140+ } ,
67141 } ) ;
68142 }
69143
70- protected parse ( file : SchemaFile ) {
144+ protected parseJS ( file : SchemaFile ) {
71145 try {
72146 return parse ( file . content , {
73147 sourceFilename : file . fileName ,
@@ -86,19 +160,25 @@ export class CubeSchemaConverter {
86160 }
87161 }
88162
89- public async generate ( ) {
90- await this . prepare ( ) ;
163+ public async generate ( cubeName ?: string ) {
164+ await this . prepare ( cubeName ) ;
91165
92166 this . converters . forEach ( ( converter ) => {
93167 converter . convert ( this . parsedFiles ) ;
94168 } ) ;
95169 }
96170
97171 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+ } ) ;
103183 }
104184}
0 commit comments