UNPKG

omelop-protoc

Version:

解析 ts 的interface 到 omelox-protobuf用的 json格式。

353 lines (328 loc) 9.5 kB
import * as path from 'path'; import { resolve } from 'path'; import * as TJS from 'typescript-json-schema'; import { Definition, JsonSchemaGenerator } from 'typescript-json-schema'; import * as fs from 'fs'; import * as util from 'util'; function log(...args) { console.log(...args); } function error(msg, ...args) { const str = util.format(msg, ...args); console.error(str); throw new Error(str); } let responseStr = '_Res'; let requestStr = '_Req'; let MergeMessage = false function walkDir(dir, handler, filters = []) { fs.readdirSync(dir).forEach(function (filename) { if (filters.indexOf(filename) !== -1) { return; } console.log('walkDir=', filename) const _path = dir + '/' + filename; const stat = fs.statSync(_path); if (stat && stat.isDirectory()) { walkDir(_path, handler, filters); } else { if (handler) { handler(_path); } } }); } /** * * @param baseDir * @param reqStr * @param resStr * @param mergeMessage message 结构放到顶层 (默认的客户端不支持,需要修改客户端) */ export function parseToOmeloxProtobuf(baseDir: string, reqStr = '_Req', resStr = '_Res', mergeMessage = false): { client: object, server: object, dictionary: string[] } { responseStr = resStr; requestStr = reqStr; MergeMessage = mergeMessage let retObj = { client: {}, server: {}, dictionary: [] }; const tsFilePaths: string[] = []; walkDir(baseDir, (val: string) => { if (!val.endsWith('.ts')) { return; } tsFilePaths.push(resolve(val)); retObj.dictionary.push(path.parse(val).name); }, ['impl']) // optionally pass argument to schema generator const settings: TJS.PartialArgs = { required: true }; // optionally pass ts compiler options const compilerOptions: TJS.CompilerOptions = { strictNullChecks: true }; const program = TJS.getProgramFromFiles(tsFilePaths, compilerOptions, baseDir); const generator = TJS.buildGenerator(program, settings); // all symbols const symbols = generator.getMainFileSymbols(program); let clientMessages = {} let serverMessages = {} walkDir(baseDir, (val: string) => { if (!val.endsWith('.ts')) { return; } if (!mergeMessage) { clientMessages = {} serverMessages = {} } const obj = parseFile(baseDir, val, program, generator, symbols, clientMessages, serverMessages); const tmp = path.parse(val); retObj.client[tmp.name] = obj.client; retObj.server[tmp.name] = obj.server; }, ['impl']) retObj.client = sortMsg(retObj.client) retObj.server = sortMsg(retObj.server) if (mergeMessage) { let obj = {} for (let k in clientMessages) { obj['message ' + k] = clientMessages[k] } obj = sortMsg(obj) for (let k in obj) { retObj.client[k] = obj[k] } obj = {} for (let k in serverMessages) { obj['message ' + k] = serverMessages[k] } obj = sortMsg(obj) for (let k in obj) { retObj.server[k] = obj[k] } } return retObj; } function sortMsg(obj) { let arr: { k: string, v: object }[] = [] for (let k in obj) { arr.push({ k: k, v: obj[k] }) } arr.sort((a, b) => { if (a.k.includes('.')) { if (b.k.includes('.')) { return a.k > b.k ? 1 : -1 } return -1 } if (b.k.includes('.')) { return 1 } return a.k > b.k ? 1 : -1 }) let newObj = {} for (let v of arr) { newObj[v.k] = v.v } return newObj } function parseFile(baseDir: string, filename: string, program: TJS.Program, generator: JsonSchemaGenerator, symbols: string[], clientMessages, serverMessages) { if (!symbols || !symbols.length) { return; } const filePath = path.parse(filename); filename = filePath.name.replace(/\./g, '_'); // const symbolName = symbols[symbols.length-1]; // if(!symbolName){ // return; // } let symbolClient; if (symbols.includes(filename + requestStr)) { symbolClient = generator.getSchemaForSymbol(filename + requestStr); } let client; let server; if (symbolClient) { client = parseSymbol(symbolClient, symbolClient, clientMessages); } let symbolServer; if (symbols.includes(filename + responseStr)) { if (!client) { console.warn('WARNING:', filename, `has ${responseStr} without ${requestStr}`); } symbolServer = generator.getSchemaForSymbol(filename + responseStr); } if (!symbolServer) { if (client) { // console.warn('WARNING:',filename,`has ${requestStr} without ${responseStr}`); } if (symbols.includes(filename)) { symbolServer = generator.getSchemaForSymbol(filename); } } if (!symbolServer) { return { client: client }; } server = parseSymbol(symbolServer, symbolServer, serverMessages); return { client: client, server: server }; // return transMessage(obj,messages); } interface IProp { $ref?: string; type?: string; additionalProperties?: string; items: { type?: string; $ref?: string }; } const PROTOBUF_TYPES = [ 'uInt32', 'sInt32', 'int32', 'double', 'string', 'message', 'float' ]; function getDefinitionFromRoot(root: Definition, ref: string): Definition { // "#/definitions/MyRank" let name: string = ref.split('/') as any; name = name[name.length - 1]; const ret = root.definitions[name]; if (!ret) { error('!find definition from root error', root, ret); } ret['name'] = name; return ret as Definition; } function normalType(typeName: string) { if (PROTOBUF_TYPES.includes(typeName)) { return typeName; } if (typeName === 'number') { return 'uInt32'; } error('!! error typeName', typeName); } function parseSymbol(root: Definition, symbol: Definition, messages: object) { /* { "type": "object", "properties": { "normalArr": { "description": "The float of the nowplayers.", "additionalProperties": "uInt32", "type": "array" }, "normalStrArr": { "type": "array", "items": { "type": "string" } }, "ranks": { "type": "array", "items": { "$ref": "#/definitions/MyRank" } }, "rk": { "$ref": "#/definitions/MyRank" }, "val": { "type": "number" } }, "required": [ "normalArr", "normalStrArr", "ranks" ], "definitions": { "MyRank": { "type": "object", "properties": { "nickname": { "type": "number" }, "ggg": { "$ref": "#/definitions/GGG" } }, "required": [ "ggg", "nickname" ] }, "GGG": { "type": "object", "properties": { "ccgg": { "type": "number" } }, "required": [ "ccgg" ] } }, "$schema": "http://json-schema.org/draft-06/schema#" } */ function parseRef(obj, key, $ref) { const definition = getDefinitionFromRoot(root, $ref); const name = (definition as any).name; if (definition.enum) { if (!definition.type) { console.log(obj, key, $ref, definition, name); throw new Error('!! un know enum type'); } let type = definition.type; if (type === 'number' || type === 'boolean') { type = 'uInt32' as any; } return ' ' + type + ' ' + key; } if (!messages[name]) { messages[name] = parseSymbol(root, definition, messages); } if (!MergeMessage) { obj['message ' + name] = messages[name]; } return ' ' + name + ' ' + key; } let val = {}; let i = 1; for (let key in symbol.properties) { const prop = <IProp>symbol.properties[key]; let msgkey = 'optional'; // 判断是否是required if (prop.type === 'array') { msgkey = 'repeated'; } else if (symbol.required && symbol.required.includes(key)) { msgkey = 'required'; } // 判断类型 type items additionalProperties if (prop.type !== 'array') { if (prop.$ref) { if (prop.type) { msgkey += ' ' + normalType(prop.type) + ' ' + key; } else { msgkey += parseRef(val, key, prop.$ref); } } else { msgkey += ' ' + normalType(prop.type) + ' ' + key; } } else { // array if (prop.additionalProperties) { msgkey += ' ' + normalType(prop.additionalProperties) + ' ' + key; } else { if (prop.items.type) { msgkey += ' ' + normalType(prop.items.type) + ' ' + key; } else { msgkey += parseRef(val, key, prop.items.$ref); } } } val[msgkey] = i++; } return val; }