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