nope/modules/mod-GRPC-Interface/helpers/analyse-functions.ts
2020-09-10 18:21:19 +02:00

284 lines
8.9 KiB
TypeScript

/**
* @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<string>).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<string>).filter(value => value ? value : undefined)[0];
}
_files.push({
package: (_package.match(REG_GETPACKAGENAME) as Array<string>).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<string>).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<any>).filter(value => value ? value : undefined)[0])[0];
}
const _srvName = (_srv.match(REG_GETSERVICENAME) as Array<string>)[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<string>)
/** 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<string>).filter(value => value ? value : undefined)[1];
/** Get the Input and Output Type */
const input = (_definition.match(REG_GET_TYPES) as Array<string>).filter(value => value ? value : undefined)[0];
const output = (_definition.match(REG_GET_TYPES) as Array<string>).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))
}