jspurefix
Version:
pure node js fix engine
198 lines (181 loc) • 7.61 kB
text/typescript
import { ICompilerSettings } from './compiler-settings'
import { FixDefinitions } from '../definition'
import { ContainedGroupField, ContainedSimpleField, ContainedComponentField } from '../contained'
import { StandardSnippet } from './standard-snippet'
import { ContainedSetType } from '../dict-primitive'
import { CompilerType } from './compiler-type'
import { Dictionary } from '../../collections'
import { ElasticBuffer, Ascii, Tags } from '../../buffer'
import * as fs from 'fs'
import * as Util from 'util'
import * as Path from 'path'
import { dispatchFields } from '../fields-dispatch'
import { reduceSet } from '../set-reduce'
export class MsgCompiler {
readonly queue: CompilerType[] = []
readonly snippets: StandardSnippet
readonly buffer: ElasticBuffer = new ElasticBuffer()
readonly completed: Dictionary<CompilerType>
constructor (public readonly definitions: FixDefinitions, public readonly settings: ICompilerSettings) {
this.completed = new Dictionary<CompilerType>()
this.snippets = new StandardSnippet(this.settings)
}
public async generate () {
const types: string[] = this.settings.types || this.definitions.message.keys()
return this.createTypes(types)
}
private getFileName (compilerType: CompilerType): string {
const settings = this.settings
const fileName = `${compilerType.snaked}.ts`
return Path.join(settings.output, fileName)
}
private async createTypes (types: string[]) {
const definitions = this.definitions
types.forEach((type: string) => {
const definition = definitions.containedSet(type)
if (!definition) {
throw new Error(`no type ${type} defined`)
}
const ct = new CompilerType(this.definitions, definition, definition.name)
this.enqueue(ct)
})
await this.work()
await this.index()
}
private async work () {
const q = this.queue
const writeFile = Util.promisify(fs.writeFile)
while (q.length > 0) {
const compilerType: CompilerType = q.pop()
const api: string = this.generateMessages(compilerType)
const fullName = this.getFileName(compilerType)
await writeFile(fullName, api, {
encoding: 'utf8'}
)
}
}
private async index () {
const writeFile = Util.promisify(fs.writeFile)
const settings = this.settings
const fileName = 'index.ts'
const done = this.completed.values()
const exports: string[] = done.reduce((prev: string[], current: CompilerType) => {
prev.push(`export * from '${current.snaked}'`)
return prev
}, [`export * from './enum'`] as string[])
const newLine = require('os').EOL
exports.sort()
exports.push('')
const api: string = exports.join(newLine)
const fullName: string = Path.join(settings.output, fileName)
await writeFile(fullName, api, {
encoding: 'utf8'}
)
}
private generateMessages (compilerType: CompilerType): string {
const definition = compilerType.set
const buffer: ElasticBuffer = this.buffer
buffer.reset()
const snippets = this.snippets
const newLine = require('os').EOL
// a single class with dependencies included
let ptr: number = this.imports(compilerType)
if (ptr > 0) {
buffer.writeString(newLine)
buffer.writeString(newLine)
}
if (this.settings.comment && definition.description) {
const comment = snippets.commentBox(definition.description)
buffer.writeString(snippets.startBlockComment(0))
buffer.writeString(newLine)
buffer.writeString(comment)
buffer.writeString(newLine)
buffer.writeString(snippets.endBlockComment(0))
buffer.writeString(newLine)
}
buffer.writeString(snippets.interface(compilerType.qualifiedName, 0))
buffer.writeChar(Ascii.Space)
buffer.writeString(snippets.startBlock(0))
buffer.writeString(newLine)
this.fields(compilerType)
buffer.writeString(snippets.endBlock(0))
buffer.writeString(newLine)
return buffer.toString()
}
// only generate once
private enqueue (ct: CompilerType): void {
const completed = this.completed
const fullName = this.getFileName(ct)
if (completed.get(fullName)) {
return
}
this.queue.push(ct)
completed.addUpdate(fullName, ct)
}
private fieldSimple (simple: ContainedSimpleField): void {
const snippets = this.snippets
const settings = this.settings
const buffer = this.buffer
const newLine = require('os').EOL
const len = buffer.writeString(snippets.simple(simple.name, Tags.toJSType(simple), simple.required, 1))
if (settings.tags) {
buffer.writeString(snippets.commentLine(`${simple.definition.tag}`, 50 - len))
}
buffer.writeString(newLine)
}
private fieldGroup (groupField: ContainedGroupField, compilerType: CompilerType): void {
const newLine = require('os').EOL
const buffer = this.buffer
const snippets = this.snippets
// for a group its field name is as defined in type, its type is qualified to avoid collisions
const extended: string = compilerType.getExtended(groupField)
buffer.writeString(snippets.group(compilerType.getFieldGroupName(groupField), extended, groupField.required, 1))
this.enqueue(new CompilerType(this.definitions, groupField.definition, extended))
buffer.writeString(newLine)
}
private fieldComponent (componentField: ContainedComponentField, compilerType: CompilerType): void {
const newLine = require('os').EOL
const buffer = this.buffer
const snippets = this.snippets
const extended: string = compilerType.getExtended(componentField)
buffer.writeString(snippets.component(extended, componentField.required, 1))
this.enqueue(new CompilerType(this.definitions, componentField.definition, extended))
buffer.writeString(newLine)
}
private fields (compilerType: CompilerType): void {
this.attributes(compilerType)
dispatchFields(compilerType.set.fields, {
group: (g: ContainedGroupField) => this.fieldGroup(g, compilerType),
simple: (simple: ContainedSimpleField) => this.fieldSimple(simple),
component: (c: ContainedComponentField) => this.fieldComponent(c, compilerType)
})
}
private attributes (compilerType: CompilerType): void {
const settings = this.settings
const newLine = require('os').EOL
const snippets = this.snippets
const buffer = this.buffer
compilerType.set.localAttribute.forEach((simple: ContainedSimpleField) => {
const len = buffer.writeString(snippets.simple(simple.definition.name, Tags.toJSType(simple), simple.required, 1))
if (settings.tags) {
buffer.writeString(snippets.commentLine(`${simple.definition.tag}`, 50 - len))
}
buffer.writeString(newLine)
})
}
private imports (compilerType: CompilerType): number {
const isMsg = compilerType.set.type === ContainedSetType.Msg
const snippets = this.snippets
const imports: string[] = reduceSet<string[]>(compilerType.set, {
component: (a: string[], c: ContainedComponentField) => {
a.push(`${snippets.import(compilerType.getExtended(c), isMsg, 0)}`)
return a
},
group: (a: string[], g: ContainedGroupField) => {
a.push(`${snippets.import(compilerType.getExtended(g), isMsg, 0)}`)
return a
}
}, [])
return this.buffer.writeString(imports.join(require('os').EOL))
}
}