1import re
2
3import inflection
4
5
6class NamedElement(object):
7    def __init__(self, **kwargs):
8        super(NamedElement, self).__init__()
9        self.name = kwargs.pop("name", "unnamed")
10        self.description = kwargs.pop("description", "")
11
12        if not isinstance(self.name, str):
13            raise AttributeError(
14                "Element interpreted by YAML parser as non-string; likely "
15                "missing quotes around original name."
16            )
17
18        self.old_namespaces = self.name.split(".")
19        self.old_classname = self.old_namespaces.pop()
20        self.namespaces = [
21            inflection.underscore(x) for x in self.old_namespaces
22        ]
23        self.classname = inflection.camelize(self.old_classname)
24
25    def __getattribute__(self, name):
26        lam = {
27            "CamelCase": lambda: inflection.camelize(self.name),
28            "camelCase": lambda: NamedElement.lower_camel_case(self.name),
29            "snake_case": lambda: inflection.underscore(self.name),
30            "SNAKE_CASE": lambda: inflection.underscore(self.name).upper(),
31        }.get(name)
32
33        if lam:
34            return NamedElement.__fixup_name(lam())
35        try:
36            return super(NamedElement, self).__getattribute__(name)
37        except Exception:
38            raise AttributeError(
39                "Attribute '%s' not found in %s.NamedElement"
40                % (name, self.__module__)
41            )
42
43    def old_cppNamespace(self, typename="server"):
44        return "::".join(self.old_namespaces) + "::" + typename
45
46    def old_cppNamespacedClass(self, typename="server"):
47        return self.old_cppNamespace(typename) + "::" + self.old_classname
48
49    def headerFile(self, typename="common"):
50        return self.name.replace(".", "/") + f"/{typename}.hpp"
51
52    def cppNamespace(self):
53        return "::".join(self.namespaces)
54
55    def cppNamespacedClass(self):
56        return self.cppNamespace() + "::" + self.classname
57
58    """ Some names are reserved in some languages.  Fixup names to avoid using
59        reserved words.
60    """
61
62    @staticmethod
63    def __fixup_name(name):
64        # List of reserved words from http://en.cppreference.com/w/cpp/keyword
65        cppReserved = frozenset(
66            {
67                "alignas",
68                "alignof",
69                "and",
70                "and_eq",
71                "asm",
72                "auto",
73                "bitand",
74                "bitor",
75                "bool",
76                "break",
77                "case",
78                "catch",
79                "char",
80                "char8_t",
81                "char16_t",
82                "char32_t",
83                "class",
84                "compl",
85                "concept",
86                "const",
87                "consteval",
88                "constexpr",
89                "constinit",
90                "const_cast",
91                "continue",
92                "co_await",
93                "co_return",
94                "co_yield",
95                "decltype",
96                "default",
97                "delete",
98                "do",
99                "double",
100                "dynamic_cast",
101                "else",
102                "enum",
103                "explicit",
104                "export",
105                "extern",
106                "false",
107                "float",
108                "for",
109                "friend",
110                "goto",
111                "if",
112                "inline",
113                "int",
114                "long",
115                "mutable",
116                "namespace",
117                "new",
118                "noexcept",
119                "not",
120                "not_eq",
121                "nullptr",
122                "operator",
123                "or",
124                "or_eq",
125                "private",
126                "protected",
127                "public",
128                "register",
129                "reinterpret_cast",
130                "requires",
131                "return",
132                "short",
133                "signed",
134                "sizeof",
135                "static",
136                "static_assert",
137                "static_cast",
138                "struct",
139                "switch",
140                "template",
141                "this",
142                "thread_local",
143                "throw",
144                "true",
145                "try",
146                "typedef",
147                "typeid",
148                "typename",
149                "union",
150                "unsigned",
151                "using",
152                "virtual",
153                "void",
154                "volatile",
155                "wchar_t",
156                "while",
157                "xor",
158                "xor_eq",
159            }
160        )
161
162        while name in cppReserved:
163            name = name + "_"
164
165        return name
166
167    # Normally you can use inflection.camelize(string, False) to return
168    # a lowerCamelCase string, but it doesn't work well for acronyms.
169    # An input like "MACAddress" will become "mACAddress".  Try to handle
170    # acronyms in a better way.
171    @staticmethod
172    def lower_camel_case(name):
173        # Get the UpperCamelCase variation of the name first.
174        upper_name = inflection.camelize(name)
175
176        # If it is all upper case, return as all lower.  ex. "MAC"
177        if re.match(r"^[A-Z0-9]*$", upper_name):
178            return upper_name.lower()
179
180        # If it doesn't start with 2 upper case, it isn't an acronym.
181        if not re.match(r"^[A-Z]{2}", upper_name):
182            return upper_name[0].lower() + upper_name[1:]
183
184        # If it is upper case followed by 'v[0-9]', treat it all as one word.
185        # ex. "IPv6Address" -> "ipv6Address"
186        if re.match(r"^[A-Z]+v[0-9]", upper_name):
187            return re.sub(
188                r"^([A-Z]+)(.*)$",
189                lambda m: m.group(1).lower() + m.group(2),
190                upper_name,
191            )
192
193        # Anything left has at least two sequential upper-case, so it is an
194        # acronym.  Switch all but the last upper-case (which starts the next
195        # word) to lower-case.
196        # ex. "MACAddress" -> "macAddress".
197        return re.sub(
198            r"^([A-Z]+)([A-Z].*)$",
199            lambda m: m.group(1).lower() + m.group(2),
200            upper_name,
201        )
202