/** * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2019-01-09 18:10:33 * @modify date 2020-09-09 08:35:46 * @desc [description] */ /** * This File provides handy tools to extract the services and packages of a proto file. * In Addition, it matches the input and outputs of a function to the function it self. * * To provide this tool, REGULAR-EXPRESSIONS are used. A good starting point and tool * to test your regular expression is provided at https://regexr.com/ * * The used REGULAR-EXPRESSIONS (in short regex) are stored in the constants below. * * A File is analyse by extracting all defined packages in the file and split up the * content based on that packages. * * ### TEXT #### * package test; * ### More TEXT ### ==> contains to Package 1 * package test2; * ### And More TEXT ### ==> Contains to Package 2 * * After extracting the content of a Package, all service Providers are extracted with * the same concept: * ### TEXT #### * package test; * ### TEXT ### * service TestService { ==> Split with this line. Analayse the following stuff until the next service * // Comments etc * rpc testCall (stream InputTypeName) returns (otherPackage.Message) {} * } * ... * * service TestService2 { ==> Split here again * // Comments etc * rpc testCall (stream InputTypeName) returns (otherPackage.Message) {} * } * ... * * Thereby all available RPCs of a Service are extracted. They are finially analyzed * and the Information is assembled in a specified Data-Structure. * */ import { readFileSync } from "fs"; import { listFiles } from "../../../lib/helpers/fileMethods"; const REG_SERVICE = /service\s*\w+\s*\{\s*\n/g; const REG_GETSERVICENAME = /\w+/g; const REG_RPC = /rpc \w+\s*\((stream)?\s*\w+\.?\w*\)\s*returns\s*\((stream)?\s*\w+\.?\w*\)\s\{\}*/g; const REG_RPC_DETAILS = /[\w+\.?]*/g const REG_GET_TYPES = /\w*[\.\w]*(?!\()(?=\))/g; const REG_PACKAGE = /package \w[\.\w]*/; const REG_GETPACKAGENAME = /[^ ]*/g; /** * Fucntion to Analyse a File and extract all Packages, Service-Providers and their RPCs with * the defined inputs and outputs * * @export * @param {string} _fileContent the content of the file * @param {*} [_ret={}] A Object which will be extended * @returns */ export function analyseFile(_fileContent: string, _ret: any = {}) { let _files = new Array<{ package: string, content: string }>(); if (REG_PACKAGE.test(_fileContent)) { const _packages = (_fileContent.match(REG_PACKAGE) as Array).filter(value => value ? value : undefined); for (const _package of _packages) { /** Extract the Content after the Service-Definition */ let _splitted = _fileContent.split(_package)[1]; /** Test if a Service follows => If so, remove the second servicer */ if (REG_PACKAGE.test(_splitted)) { _splitted = (_splitted.match(REG_PACKAGE) as Array).filter(value => value ? value : undefined)[0]; } _files.push({ package: (_package.match(REG_GETPACKAGENAME) as Array).filter(value => value ? value : undefined)[1], content: _splitted }); } } else { _files.push({ package: '', content: _fileContent }); } return getServiceProviders(_files, _ret); } /** * Function to extract the Service Provider * * @param {Array<{ package: string, content: string }>} packages List with Packages * @param _ret Element which will contain the information * @returns */ function getServiceProviders(packages: Array<{ package: string, content: string }>, _ret: { /** Package */ [index: string]: { /** Servicer */ [index: string]: { methods: { /** Method */ [index: string]: { input: { path: string, stream: boolean, }, output: { path: string, stream: boolean, }, } } } } }) { for (const _package of packages) { if (REG_SERVICE.test(_package.content)) { const _services = (_package.content.match(REG_SERVICE) as Array).filter(value => value ? value : undefined); if (_ret[_package.package] === undefined) { _ret[_package.package] = { } } for (const _srv of _services) { /** Extract the Content after the Service-Definition */ let _splitted = _package.content.split(_srv)[1]; /** Test if a Service follows => If so, remove the second servicer */ if (REG_SERVICE.test(_splitted)) { _splitted = _splitted.split((_splitted.match(REG_SERVICE) as Array).filter(value => value ? value : undefined)[0])[0]; } const _srvName = (_srv.match(REG_GETSERVICENAME) as Array)[1]; if (_ret[_package.package][_srvName] === undefined) { _ret[_package.package][_srvName] = { methods: {} } } /** Extract all RPCs */ if (REG_RPC.test(_splitted)) { const rpcs = (_splitted.match(REG_RPC) as Array) /** Filter the results to remove undefined Elements */ .filter(value => value ? value : undefined); for (const _rpc of rpcs) { _ret[_package.package][_srvName].methods = Object.assign( _ret[_package.package][_srvName].methods, analyseRpc(_rpc, _package.package) ); } } } } } return _ret; } /** * Function for analysing a string like: 'rpc get_position_stream (std_package.Frequency) returns (stream std_package.Point) {}' * To extract the Definition * * @param {string} _definition * @param {string} _package * @returns */ function analyseRpc(_definition: string, _package: string) { /** Extract the Name */ const name = (_definition.match(REG_RPC_DETAILS) as Array).filter(value => value ? value : undefined)[1]; /** Get the Input and Output Type */ const input = (_definition.match(REG_GET_TYPES) as Array).filter(value => value ? value : undefined)[0]; const output = (_definition.match(REG_GET_TYPES) as Array).filter(value => value ? value : undefined)[1]; /** Split with the Input type, to figure out whether something is streaming or not */ const _splitted = _definition.split(input); /** Check for the corresponding keyword */ const streamInput = _splitted[0].match(/\(\s*stream/) !== null; const streamOutput = _splitted[1].match(/\(\s*stream/) !== null; /** Define the Return Type */ const _ret: { [index: string]: { /** Input of the Method */ input: { path: string, stream: boolean, }, /** Output of the Method */ output: { path: string, stream: boolean, }, } } = {}; _ret[name] = { input: { path: 'definitions.' + (input.includes('.') ? input : _package + '.' + input), stream: streamInput, }, output: { path: 'definitions.' + (output.includes('.') ? output : _package + '.' + output), stream: streamOutput, }, }; return _ret; } export async function scanAllFiles(_dir: string = './Proto-Repository/protos', _elements: { /** Package */ [index: string]: { /** Servicer */ [index: string]: { methods: { /** Method */ [index: string]: { input: { path: string, stream: boolean, }, output: { path: string, stream: boolean, }, } } } } } = {}) { /** Scan fro Proto-Files in the given Dir */ const files = await listFiles(_dir, '.proto'); /** Load all Files and analyse them */ for (const file of files) { /** Read-In the File */ const _file = readFileSync(file, { encoding: 'utf-8' }); _elements = (analyseFile(_file, _elements)); } /** Return the result */ return _elements; } if (require.main == module) { let file = 'C:\\Users\\m.karkowski\\Documents\\Repos\\ZeMA-Kernel\\Proto-Repository\\protos\\robot.proto'; let content = readFileSync(file, { encoding: 'utf-8' }) console.log(analyseFile(content)) }