protoc-gen-defaults.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # !/usr/bin/env python
  2. import os
  3. import logging
  4. import json
  5. from pathlib import Path
  6. from typing import Dict, List
  7. from google.protobuf.compiler import plugin_pb2 as plugin
  8. from google.protobuf.message_factory import GetMessageClass
  9. from google.protobuf.descriptor_pb2 import FileDescriptorProto, DescriptorProto, FieldDescriptorProto,FieldOptions
  10. from google.protobuf.descriptor import FieldDescriptor, Descriptor, FileDescriptor
  11. from ProtoElement import ProtoElement
  12. from ProtocParser import ProtocParser
  13. logger = logging.getLogger(__name__)
  14. logging.basicConfig(
  15. level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
  16. )
  17. class BinDefaultsParser(ProtocParser) :
  18. def start_message(self,message:ProtoElement) :
  19. super().start_message(message)
  20. def end_message(self,message:ProtoElement):
  21. super().end_message(message)
  22. self.has_error = False
  23. default_structure = message.render()
  24. if not default_structure:
  25. logger.warn(f'No default values for {message.name}')
  26. return
  27. respfile = self.response.file.add()
  28. outfilename = f'{message.name}_defaults_pb.bin'
  29. with open(os.path.join(self.param_dict.get('defaultspath','.'),outfilename), 'wb') as bin_file:
  30. res = default_structure.SerializeToString()
  31. bin_file.write(res)
  32. logger.info(f'Wrote {bin_file.name}')
  33. respfile.name = f'{outfilename}.gen'
  34. logger.info(f"Creating binary file for defaults: {respfile.name}")
  35. respfile.content = f'Content written to {respfile.name}'
  36. def start_file(self,file:FileDescriptor) :
  37. super().start_file(file)
  38. def end_file(self,file:ProtoElement) :
  39. super().end_file(file)
  40. def get_name(self)->str:
  41. return 'protoc_plugin_defaults'
  42. def add_comment_if_exists(element, comment_type: str, path: str) -> dict:
  43. comment = getattr(element, f"{comment_type}_comment", "").strip()
  44. return {f"__{comment_type}_{path}": comment} if comment else {}
  45. def repeated_render(self,element:ProtoElement,obj:any):
  46. return [obj] if element.repeated else obj
  47. def render(self,element: ProtoElement):
  48. if len(element.childs)>0:
  49. oneof = getattr(element.descriptor,'containing_oneof',None)
  50. if oneof:
  51. # we probably shouldn't set default values here
  52. pass
  53. has_render = False
  54. for child in element.childs:
  55. rendered = child.render()
  56. if rendered:
  57. has_render = True
  58. # try:
  59. if child.repeated:
  60. try:
  61. getattr(element.message_instance,child.name).extend(rendered)
  62. except:
  63. getattr(element.message_instance,child.name).extend( [rendered])
  64. elif child.type == FieldDescriptor.TYPE_MESSAGE:
  65. getattr(element.message_instance,child.name).CopyFrom(rendered)
  66. else:
  67. setattr(element.message_instance,child.name,rendered)
  68. # except:
  69. # logger.error(f'Unable to assign value from {child.fullname} to {element.fullname}')
  70. element.message_instance.SetInParent()
  71. if not has_render:
  72. return None
  73. else:
  74. default_value = element._default_value
  75. options = element.options['cust_field'] if 'cust_field' in element.options else None
  76. msg_options = element.options['cust_msg'] if 'cust_msg' in element.options else None
  77. init_from_mac = getattr(options,'init_from_mac', False) or getattr(msg_options,'init_from_mac', False)
  78. default_value = getattr(options,'default_value', None)
  79. global_name = getattr(options,'global_name', None)
  80. const_prefix = getattr(options,'const_prefix', self.param_dict['const_prefix'])
  81. if init_from_mac:
  82. default_value = f'{const_prefix}@@init_from_mac@@'
  83. elif default_value:
  84. if element.descriptor.cpp_type == FieldDescriptor.CPPTYPE_STRING:
  85. default_value = default_value
  86. elif element.descriptor.cpp_type == FieldDescriptor.CPPTYPE_ENUM:
  87. try:
  88. default_value = element.enum_values.index(default_value)
  89. except:
  90. raise ValueError(f'Invalid default value {default_value} for {element.path}')
  91. elif element.descriptor.cpp_type in [FieldDescriptor.CPPTYPE_INT32, FieldDescriptor.CPPTYPE_INT64,
  92. FieldDescriptor.CPPTYPE_UINT32, FieldDescriptor.CPPTYPE_UINT64]:
  93. int_value = int(default_value)
  94. if element.descriptor.cpp_type in [FieldDescriptor.CPPTYPE_UINT32, FieldDescriptor.CPPTYPE_UINT64] and int_value < 0:
  95. raise ValueError(f"Negative value for unsigned int type trying to assign {element.path} = {default_value}")
  96. default_value = int_value
  97. elif element.descriptor.cpp_type in [FieldDescriptor.CPPTYPE_DOUBLE, FieldDescriptor.CPPTYPE_FLOAT]:
  98. float_value = float(default_value)
  99. if '.' not in default_value:
  100. raise ValueError(f"Integer string for float/double type trying to assign {element.path} = {default_value}")
  101. default_value = float_value
  102. elif element.descriptor.cpp_type == FieldDescriptor.CPPTYPE_BOOL:
  103. if default_value.lower() in ['true', 'false']:
  104. default_value = default_value.lower() == 'true'
  105. else:
  106. raise ValueError(f'Invalid boolean value trying to assign {element.path} = {default_value}')
  107. if default_value:
  108. element.message_instance.SetInParent()
  109. return self.repeated_render(element,default_value) if default_value else None
  110. return element.message_instance
  111. if __name__ == '__main__':
  112. data = ProtocParser.get_data()
  113. logger.info(f"Generating binary files for defaults")
  114. protocParser:BinDefaultsParser = BinDefaultsParser(data)
  115. protocParser.process()
  116. logger.debug('Done generating JSON file(s)')