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