UNPKG

10.6 kBtext/coffeescriptView Raw
1fs = require 'fs'
2Path = require 'path'
3Path.isAbsolute ?= (str) -> str.charAt(0) is '/'
4{ camelize } = require './util'
5
6Facade = require './main'
7{ requireFile } = Facade
8{ Base, BaseModel } = Facade
9MasterDataResource = require './master-data-resource'
10
11
12class ClassInfo
13 constructor: (@name, @relPath, @className, @moduleName) ->
14
15 Object.defineProperties @::,
16 modFullName: get: ->
17 if @moduleName
18 @moduleName + '/' + @name
19 else
20 @name
21
22 fullClassName: get: ->
23 camelize(@moduleName) + @className
24
25
26
27class EntryGeneratorInput
28
29 constructor: (facadePath, dirname, outfile) ->
30 @validate(facadePath, dirname, outfile)
31
32 @absDirname = @absolutePath(dirname)
33
34 # public data
35 @absOutfilePath = @absolutePath(outfile)
36 @facadePath = @relativePath(facadePath)
37 @coreClasses = @getClassInfoList(@absDirname)
38 @modules = @getModulesClasses()
39 @facadeClassName = requireFile(@absolutePath(facadePath)).name
40 @facade = @createFacade()
41 @masterJSONStr = JSON.stringify @getMasterJSON()
42 @factories = @getPreferredFactoryNames()
43
44
45 createFacade: ->
46 allModules = {}
47 for moduleName in @getModuleNames()
48 allModules[moduleName] = Path.join(@absDirname, moduleName)
49
50 return Facade.createInstance
51 dirname: @absDirname
52 modules: allModules
53 master: true
54
55
56
57 ###*
58 @return {Array(ClassInfo)}
59 ###
60 getClassInfoList: (dirPath, moduleName = '') ->
61
62 relDirname = @relativePath(dirPath)
63
64 for filename in @getClassFiles(dirPath)
65 name = filename.split('.')[0]
66 relPath = relDirname + '/' + name
67 className = requireFile(Path.resolve dirPath, name).name
68 new ClassInfo(name, relPath, className, moduleName)
69
70
71 ###*
72 @return {{[string]: Array(ClassInfo)}}
73 ###
74 getModulesClasses: ->
75
76 modules = {}
77
78 for moduleName in @getModuleNames()
79 modulePath = Path.join(@absDirname, moduleName)
80 modules[moduleName] = @getClassInfoList(modulePath, moduleName)
81
82 return modules
83
84
85 ###*
86 @method getMasterJSON
87 @private
88 @return {Object} master data
89 ###
90 getMasterJSON: ->
91
92 try
93 { masterJSONPath } = @facade.master
94
95 return null if not fs.existsSync(masterJSONPath)
96
97 return require(masterJSONPath)
98
99 catch e
100 return null
101
102
103 ###*
104 @return {Array(string)} array of module names
105 ###
106 getModuleNames: ->
107
108 fs.readdirSync(@absDirname)
109 .filter (subDirName) -> subDirName isnt 'master-data'
110 .filter (subDirName) -> subDirName isnt 'custom-roles'
111 .map (subDirname) => Path.join @absDirname, subDirname
112 .filter (subDirPath) -> fs.statSync(subDirPath).isDirectory()
113 .filter (subDirPath) ->
114 fs.readdirSync(subDirPath).some (filename) ->
115 klass = requireFile Path.join(subDirPath, filename)
116 klass.isBaseDomainClass
117 .map (subDirPath) -> Path.basename(subDirPath)
118
119
120 ###*
121 get domain files to load
122
123 @method getClassFiles
124 @private
125 @return {Array(string)} filenames
126 ###
127 getClassFiles: (path) ->
128
129 fileInfoDict = {}
130
131 for filename in fs.readdirSync(path)
132
133 [ name, ext ] = filename.split('.')
134 continue if ext not in ['js', 'coffee']
135
136 klass = requireFile path + '/' + filename
137
138 fileInfoDict[name] = filename: filename, klass: klass
139
140 files = []
141 for name, fileInfo of fileInfoDict
142
143 { klass, filename } = fileInfo
144 continue if filename in files
145
146 ParentClass = Object.getPrototypeOf(klass::).constructor
147
148 if ParentClass.className and pntFileName = fileInfoDict[ParentClass.getName()]?.filename
149
150 files.push pntFileName unless pntFileName in files
151
152 files.push filename
153
154 return files
155
156
157 ###*
158 get entities with no factory
159 ###
160 getPreferredFactoryNames: ->
161 factories = {}
162
163 for classInfo in @coreClasses
164 factories[classInfo.modFullName] = @getPreferredFactoryName(classInfo)
165
166 for modName, classes of @modules
167 for classInfo in classes
168 factories[classInfo.modFullName] = @getPreferredFactoryName(classInfo)
169
170 delete factories[k] for k, v of factories when not v?
171 return factories
172
173
174 getPreferredFactoryName: (classInfo) ->
175 ModelClass = @facade.require(classInfo.modFullName)
176 return if (ModelClass::) not instanceof BaseModel
177 try
178 factory = @facade.createPreferredFactory(classInfo.modFullName)
179 return "'#{factory.constructor.className}'"
180 catch e
181 return 'null'
182
183
184
185 ###*
186 validate input data
187 ###
188 validate: (facadePath, dirname, outfile) ->
189 absFacadePath = @absolutePath facadePath
190 absDirname = @absolutePath dirname
191 outDir = Path.dirname(@absolutePath outfile)
192
193 throw new Error("'#{absFacadePath}' is not found.") if not fs.existsSync(absFacadePath)
194 throw new Error("dirname: '#{absDirname}' is not found.") if not fs.existsSync(absDirname)
195 throw new Error("output directory: '#{outDir}' is not found.") if not fs.existsSync(outDir)
196
197
198 absolutePath: (path) ->
199 return Path.resolve(path) if Path.isAbsolute path
200 return Path.resolve process.cwd(), path
201
202
203 relativePath: (path) ->
204 relPath = Path.relative(Path.dirname(@absOutfilePath), path)
205
206 if relPath.charAt(0) isnt '.'
207 relPath = './' + relPath
208
209 return relPath
210
211
212
213class EntryGenerator
214
215 @generate: (facadePath, dirname, outfile, esCode = false) ->
216
217 input = new EntryGeneratorInput(facadePath, dirname, outfile)
218
219 if esCode
220 generator = new ESCodeGenerator(input)
221 else
222 generator = new JSCodeGenerator(input)
223
224 generator.generate()
225
226
227 constructor: (@input) ->
228
229
230 generate: ->
231
232 code = [
233 @getPragmas()
234 @getImportStatements()
235 @getPackedData()
236 @getExportStatements()
237 ].join('\n') + '\n'
238
239 fs.writeFileSync(@input.absOutfilePath, code)
240
241 getPragmas: -> ''
242
243
244 getPackedData: ->
245
246 { factories, coreClasses, modules, masterJSONStr, facadeClassName } = @input
247
248 """
249 const packedData = {
250 // eslint-disable-next-line quotes, key-spacing, object-curly-spacing, comma-spacing
251 masterData : #{masterJSONStr},
252 core: {
253 #{@getPackedCode(coreClasses, 2)},
254 },
255 modules: {
256 #{@getModulesPackedData(modules)}
257 },
258 factories: {
259 #{@getFactoriesPackedData(factories, 2)}
260 }
261 }
262 #{facadeClassName}.prototype.init = function init() { return this.initWithPacked(packedData) }
263 """
264
265 getPackedCode: (classes, indent) ->
266 spaces = [0...indent * 4].map((x) -> ' ').join('')
267
268 spaces + classes.map (classInfo) ->
269 "'#{classInfo.name}': #{classInfo.fullClassName}"
270 .join(',\n' + spaces)
271
272
273 getModulesPackedData: (modules) ->
274 _ = ' '
275 Object.keys(modules).map (modName) =>
276 modClasses = modules[modName]
277 """
278 #{_}'#{modName}': {
279 #{@getPackedCode(modClasses, 3)}
280 #{_}}
281 """
282 .join(',\n')
283
284 getFactoriesPackedData: (factories, indent) ->
285 spaces = [0...indent * 4].map((x) -> ' ').join('')
286
287 spaces + Object.keys(factories).map (modelName) =>
288 factoryName = factories[modelName]
289 return "'#{modelName}': #{factoryName}"
290 .join(',\n' + spaces)
291
292class JSCodeGenerator extends EntryGenerator
293
294 getPragmas: ->
295 """
296 /* eslint quote-props: 0, object-shorthand: 0, no-underscore-dangle: 0 */
297 const __ = function __(m) { return m.default ? m.default : m }
298 """
299
300 getImportStatements: ->
301
302 { coreClasses, modules, facadePath, facadeClassName } = @input
303
304 # importing modules
305 code = @getRequireStatement(facadeClassName, facadePath)
306 code += @getRequireStatement(classInfo.className, classInfo.relPath) for classInfo in coreClasses
307 for modName, modClasses of modules
308 code += @getRequireStatement(classInfo.fullClassName, classInfo.relPath) for classInfo in modClasses
309
310 return code
311
312
313 getExportStatements: ->
314
315 { coreClasses, modules, facadeClassName } = @input
316
317 classNames = coreClasses.map((coreClass) -> coreClass.className)
318 for modName, modClasses of modules
319 classNames = classNames.concat modClasses.map (modClass) -> modClass.fullClassName
320
321 keyValues = classNames.map (className) ->
322 "#{className}: #{className}"
323
324
325 return """
326 module.exports = {
327 #{facadeClassName}: #{facadeClassName},
328 #{keyValues.join(',\n ')}
329 }
330 """
331
332 getRequireStatement: (className, path) ->
333 return "const #{className} = __(require('#{path}'))\n"
334
335
336
337class ESCodeGenerator extends EntryGenerator
338
339 getPragmas: ->
340 """
341 // @flow
342 /* eslint quote-props: 0, max-len: 0 */
343 """
344
345
346 getImportStatements: ->
347
348 { coreClasses, modules, facadePath, facadeClassName } = @input
349
350 code = @getImportStatement(facadeClassName, facadePath)
351 code += @getImportStatement(classInfo.className, classInfo.relPath) for classInfo in coreClasses
352 for modName, modClasses of modules
353 code += @getImportStatement(classInfo.fullClassName, classInfo.relPath) for classInfo in modClasses
354 return code
355
356
357
358 getExportStatements: ->
359
360 { coreClasses, modules, facadeClassName } = @input
361
362 classNames = coreClasses.map((coreClass) -> coreClass.className)
363 for modName, modClasses of modules
364 classNames = classNames.concat modClasses.map (modClass) -> modClass.fullClassName
365
366
367 return """
368 export default #{facadeClassName}
369 export {
370 #{facadeClassName},
371 #{classNames.join(',\n ')}
372 }
373 """
374
375 ###*
376 get import statement from className and path
377 ###
378 getImportStatement: (className, path) ->
379 return "import #{className} from '#{path}'\n"
380
381
382module.exports = EntryGenerator