| # Copyright 2020-present Open Networking Foundation |
| # Original copyright 2020-present ADTRAN, Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| import os |
| import re |
| import glob |
| import json |
| import argparse |
| |
| try: |
| from . import protobuf_parse as parser |
| except ImportError: |
| import protobuf_parse as parser |
| |
| __version__ = '1.0' |
| |
| USAGE = """ProtoBuf -- Parser of Protocol Buffer to create the input JSON file for the library |
| |
| Usage: grpc_robot.protop [options] target_version |
| |
| ProtoBuf parser can be used to parse ProtoBuf files (*.proto) into a json formatted input file |
| for the grpc_robot library to be used for keyword documentation. |
| |
| """ |
| |
| EPILOG = """ |
| Example |
| ======= |
| # Executing `grpc_robot.protop` module using Python. |
| $ grpc_robot.protop -i /home/user/Workspace/grpc/proto/dmi 0.9.1 |
| """ |
| |
| |
| class ProtoBufParser(object): |
| |
| def __init__(self, target, target_version, input_dir, output_dir=None): |
| |
| super().__init__() |
| |
| self.target = target |
| self.target_version = target_version.replace('.', '_') |
| self.input_dir = input_dir |
| self.output_dir = output_dir |
| |
| @staticmethod |
| def read_enum(enum, protobuf_dict, module): |
| enum_dict = {'name': enum.name, 'type': 'enum', 'module': module, 'values': {ef.value: ef.name for ef in enum.body}} |
| protobuf_dict['data_types'].append(enum_dict) |
| |
| def read_message(self, message, protobuf_dict, module): |
| message_dict = {'name': message.name, 'type': 'message', 'module': module, 'fields': []} |
| |
| for f in message.body: |
| |
| if f is None: |
| continue |
| |
| if isinstance(f, parser.Enum): |
| self.read_enum(f, protobuf_dict, module) |
| continue |
| |
| elif isinstance(f, parser.Message): |
| self.read_message(f, protobuf_dict, module) |
| continue |
| |
| field_dict = {'name': f.name, 'is_choice': isinstance(f, parser.OneOf)} |
| |
| if isinstance(f, parser.Field): |
| field_dict['repeated'] = f.repeated |
| |
| try: |
| field_dict['type'] = f.type._value_ |
| field_dict['lookup'] = False |
| except AttributeError: |
| field_dict['type'] = f.type |
| field_dict['lookup'] = True |
| |
| elif isinstance(f, parser.OneOf): |
| field_dict['cases'] = [] |
| for c in f.fields: |
| case_dict = {'name': c.name} |
| try: |
| case_dict['type'] = c.type._value_ |
| case_dict['lookup'] = False |
| except AttributeError: |
| case_dict['type'] = c.type |
| case_dict['lookup'] = True |
| field_dict['cases'].append(case_dict) |
| |
| message_dict['fields'].append(field_dict) |
| |
| protobuf_dict['data_types'].append(message_dict) |
| |
| def parse_files(self): |
| |
| protobuf_dict = { |
| 'modules': [], |
| 'data_types': [], |
| 'services': [] |
| } |
| |
| for file_name in glob.glob(os.path.join(self.input_dir, '*.proto')): |
| print(file_name) |
| |
| module = os.path.splitext(os.path.basename(file_name))[0] |
| module_dict = {'name': module, 'imports': []} |
| |
| # the protobuf parser can not handle comments "// ...", so remove them first from the file |
| file_content = re.sub(r'\/\/.*', '', open(file_name).read()) |
| parsed = parser.proto.parse(file_content) |
| |
| # print(parsed.statements) |
| |
| for p in parsed.statements: |
| # print(p) |
| |
| if isinstance(p, parser.Import): |
| module_dict['imports'].append(os.path.splitext(os.path.basename(p.identifier))[0]) |
| |
| elif isinstance(p, parser.Enum): |
| self.read_enum(p, protobuf_dict, module) |
| |
| elif isinstance(p, parser.Message): |
| self.read_message(p, protobuf_dict, module) |
| |
| elif isinstance(p, parser.Service): |
| service_dict = {'name': p.name, 'module': module, 'rpcs': []} |
| |
| for field in p.body: |
| |
| if isinstance(field, parser.Enum): |
| self.read_enum(field, protobuf_dict, module) |
| |
| elif isinstance(field, parser.Message): |
| self.read_message(field, protobuf_dict, module) |
| |
| elif isinstance(field, parser.Rpc): |
| rpc_dict = {'name': field.name, 'request': {}, 'response': {}} |
| |
| for attr in ['request', 'response']: |
| try: |
| rpc_dict[attr]['is_stream'] = field.__getattribute__('%s_stream' % attr) |
| |
| try: |
| rpc_dict[attr]['type'] = field.__getattribute__('%s_message_type' % attr)._value_ |
| rpc_dict[attr]['lookup'] = False |
| except AttributeError: |
| rpc_dict[attr]['type'] = field.__getattribute__('%s_message_type' % attr) |
| rpc_dict[attr]['lookup'] = not rpc_dict[attr]['type'].lower().startswith('google.protobuf.') |
| |
| except AttributeError: |
| rpc_dict[attr] = None |
| |
| service_dict['rpcs'].append(rpc_dict) |
| |
| protobuf_dict['services'].append(service_dict) |
| |
| protobuf_dict['modules'].append(module_dict) |
| |
| if self.output_dir is not None: |
| json_file_name = os.path.join(self.output_dir, self.target, '%s_%s' % (self.target, self.target_version), '%s.json' % self.target) |
| json.dump(protobuf_dict, open(json_file_name, 'w')) |
| |
| return protobuf_dict |
| |
| |
| base_dir = os.path.dirname(os.path.realpath(__file__)) |
| output_base_dir = os.path.join(os.path.split(base_dir)[:-1][0], 'services') |
| |
| |
| def main(): |
| # create commandline parser |
| arg_parse = argparse.ArgumentParser(description=USAGE, epilog=EPILOG, formatter_class=argparse.RawTextHelpFormatter) |
| |
| # add parser options |
| arg_parse.add_argument('target', choices=['dmi', 'voltha'], |
| help="Target type of which the ProtocolBuffer files shall be converted to the JSON file.") |
| arg_parse.add_argument('target_version', help="Version number of the ProtocolBuffer files.") |
| |
| arg_parse.add_argument('-i', '--inputdir', default=os.getcwd(), help="Path to the location of the ProtocolBuffer files.") |
| arg_parse.add_argument('-o', '--outputdir', default=os.getcwd(), help="Path to the location JSON file to be stored.") |
| |
| arg_parse.add_argument('-v', '--version', action='version', version=__version__) |
| arg_parse.set_defaults(feature=False) |
| |
| # parse commandline |
| args = arg_parse.parse_args() |
| |
| ProtoBufParser(args.target, args.target_version, args.inputdir or os.getcwd(), args.outputdir or output_base_dir).parse_files() |
| |
| |
| if __name__ == '__main__': |
| main() |