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