You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_clad.py 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # Copyright (c) 2016-2017 Anki, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License in the file LICENSE.txt or at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. __all__ = ['CladEnumWrapper']
  15. import sys
  16. from . import event
  17. from . import logger
  18. from cozmoclad.clad.externalInterface import messageEngineToGame as messageEngineToGame
  19. from cozmoclad.clad.externalInterface import messageGameToEngine as messageGameToEngine
  20. # Shortcut access to CLAD classes
  21. _clad_to_engine_anki = messageGameToEngine.Anki
  22. _clad_to_engine_cozmo = messageGameToEngine.Anki.Cozmo
  23. _clad_to_engine_iface = messageGameToEngine.Anki.Cozmo.ExternalInterface
  24. _clad_to_game_anki = messageEngineToGame.Anki
  25. _clad_to_game_cozmo = messageEngineToGame.Anki.Cozmo
  26. _clad_to_game_iface = messageEngineToGame.Anki.Cozmo.ExternalInterface
  27. # Register event types for engine to game messages
  28. # (e.g. _MsgObjectMoved)
  29. for _name in vars(_clad_to_game_iface.MessageEngineToGame.Tag):
  30. attrs = {
  31. '__doc__': 'Internal protocol message',
  32. 'msg': 'Message data'
  33. }
  34. _name = '_Msg' + _name
  35. cls = event._register_dynamic_event_type(_name, attrs)
  36. globals()[_name] = cls
  37. def _all_caps_to_pascal_case(name):
  38. # Convert a string from CAPS_CASE_WORDS to PascalCase (e.g. CapsCaseWords)
  39. ret_str = ""
  40. first_char = True
  41. # Build the return string
  42. for char in name:
  43. if char == "_":
  44. # skip underscores, but reset that next char will be start of a new word
  45. first_char = True
  46. else:
  47. # First letter of a word is uppercase, rest are lowercase
  48. if first_char:
  49. ret_str += char.upper()
  50. first_char = False
  51. else:
  52. ret_str += char.lower()
  53. return ret_str
  54. class CladEnumWrapper:
  55. """Subclass this for an easy way to wrap a clad-enum in a documentable class.
  56. Call cls._init_class() after declaration of the sub-class to verify the
  57. type after construction and set up id to type mapping.
  58. """
  59. # Override this to the CLAD enum type being wrapped
  60. _clad_enum = None
  61. # Override this with the type used for each instance
  62. # e.g. collections.namedtuple('_ClassName', 'name id')
  63. _entry_type = None
  64. _id_to_entry_type = None # type: dict
  65. @classmethod
  66. def find_by_id(cls, id):
  67. return cls._id_to_entry_type.get(id)
  68. @classmethod
  69. def _verify(cls, warn_on_missing_definitions=True, add_missing_definitions=True):
  70. """Verify that definitions are in sync with the underlying CLAD values.
  71. Optionally also warn about and/or add any missing definitions.
  72. Args:
  73. warn_on_missing_definitions (bool): True to warn about any entries
  74. in the underlying CLAD enum that haven't been explicitly
  75. declared (includes suggested format for adding, which can then
  76. be documented with `#:` comments for the generated docs.
  77. add_missing_definitions (bool): True to automatically add any
  78. entries in the underlying CLAD enum that haven't been explicitly
  79. declared. Note that these definitions will work at runtime, but
  80. won't be present in the auto-generated docs.
  81. """
  82. missing_definitions_message = None
  83. for (_name, _id) in cls._clad_enum.__dict__.items():
  84. # Ignore any private entries (or internal Python objects) and any
  85. # "Count" entries in the enum
  86. if not _name.startswith('_') and (_name != 'Count') and _id >= 0:
  87. attr = getattr(cls, _name, None)
  88. if attr is None:
  89. # Try valid, but less common, alternatives of the name -
  90. # leading underscores for private vars, and/or PascalCase
  91. # when the Clad type is in CAPS_CASE
  92. alternative_names = ["_" + _name]
  93. is_upper_case = _name == _name.upper()
  94. if is_upper_case:
  95. pascal_case_name = _all_caps_to_pascal_case(_name)
  96. alternative_names.extend([pascal_case_name,
  97. "_" + pascal_case_name])
  98. alternative_names.append(_name.replace("_",""))
  99. for alt_name in alternative_names:
  100. attr = getattr(cls, alt_name, None)
  101. if attr is not None:
  102. break
  103. if attr is not None:
  104. if attr.id != _id:
  105. sys.exit(
  106. 'Incorrect definition in %s for id %s=%s, (should =%s) - line should read:\n'
  107. '%s = _entry_type("%s", _clad_enum.%s)'
  108. % (str(cls), _name, attr.id, _id, _name, _name, _name))
  109. else:
  110. if warn_on_missing_definitions:
  111. if missing_definitions_message is None:
  112. missing_definitions_message = ('Missing definition(s) in %s - to document them add:' % str(cls))
  113. missing_definitions_message += ('\n %s = _entry_type("%s", _clad_enum.%s)' % (_name, _name, _name))
  114. if is_upper_case:
  115. missing_definitions_message += ('\n or %s = _entry_type("%s", _clad_enum.%s)' % (pascal_case_name, pascal_case_name, _name))
  116. if add_missing_definitions:
  117. setattr(cls, _name, cls._entry_type(_name, _id))
  118. if missing_definitions_message is not None:
  119. logger.warning(missing_definitions_message)
  120. @classmethod
  121. def _build_id_to_entry_type(cls):
  122. # populate _id_to_entry_type mapping
  123. cls._id_to_entry_type = dict()
  124. for (_name, _entry) in cls.__dict__.items():
  125. if isinstance(_entry, cls._entry_type):
  126. cls._id_to_entry_type[_entry.id] = _entry
  127. @classmethod
  128. def _init_class(cls, warn_on_missing_definitions=True, add_missing_definitions=True):
  129. cls._verify(warn_on_missing_definitions, add_missing_definitions)
  130. cls._build_id_to_entry_type()