1 | fs = require 'fs'
|
2 | Path = require 'path'
|
3 | Path.isAbsolute ?= (str) -> str.charAt(0) is '/'
|
4 | { camelize } = require './util'
|
5 |
|
6 | Facade = require './main'
|
7 | { requireFile } = Facade
|
8 | { Base, BaseModel } = Facade
|
9 | MasterDataResource = require './master-data-resource'
|
10 |
|
11 |
|
12 | class 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 |
|
27 | class EntryGeneratorInput
|
28 |
|
29 | constructor: (facadePath, dirname, outfile) ->
|
30 | @validate(facadePath, dirname, outfile)
|
31 |
|
32 | @absDirname = @absolutePath(dirname)
|
33 |
|
34 |
|
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 |
|
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 |
|
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 |
|
87 |
|
88 |
|
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 |
|
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 |
|
122 |
|
123 |
|
124 |
|
125 |
|
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 |
|
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 |
|
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 |
|
213 | class 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 |
|
292 | class 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 |
|
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 |
|
337 | class 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 |
|
377 |
|
378 | getImportStatement: (className, path) ->
|
379 | return "import #{className} from '#{path}'\n"
|
380 |
|
381 |
|
382 | module.exports = EntryGenerator
|