xref: /openbmc/bmcweb/scripts/generate_schema_enums.py (revision d1a3caa4eda8ca07cd656b4e4e8aa1e4ffe53e1e)
1#!/usr/bin/python3
2import os
3import re
4import shutil
5import xml.etree.ElementTree as ET
6from collections import defaultdict
7
8SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
9REDFISH_SCHEMA_DIR = os.path.realpath(
10    os.path.join(SCRIPT_DIR, "..", "redfish-core", "schema")
11)
12
13OUTFOLDER = os.path.realpath(
14    os.path.join(
15        SCRIPT_DIR, "..", "redfish-core", "include", "generated", "enums"
16    )
17)
18
19# Odata string types
20EDMX = "{http://docs.oasis-open.org/odata/ns/edmx}"
21EDM = "{http://docs.oasis-open.org/odata/ns/edm}"
22
23
24class Enum:
25    def __init__(self, name, values, namespace, from_file):
26        self.name = name
27        self.values = values
28        self.namespace = namespace
29        self.from_file = from_file
30
31
32def parse_schema(element, filename):
33    EntityTypes = []
34    namespace = element.attrib["Namespace"]
35    for schema_element in element:
36        name = schema_element.attrib.get("Name", None)
37        if name is None:
38            continue
39        if schema_element.tag == EDM + "EnumType":
40            enums = []
41            for member in schema_element.findall(EDM + "Member"):
42                enums.append(member.attrib["Name"])
43            EntityTypes.append(Enum(name, enums, namespace, filename))
44        if schema_element.tag == EDM + "TypeDefinition":
45            enums = []
46            for annotation in schema_element:
47                for collection in annotation:
48                    for record in collection.findall(EDM + "Record"):
49                        for member in record.findall(EDM + "PropertyValue"):
50                            enums.append(member.attrib["String"])
51            EntityTypes.append(Enum(name, enums, namespace, filename))
52    return EntityTypes
53
54
55def parse_file(filename):
56    print(f"Parsing {filename}")
57    tree = ET.parse(filename)
58    root = tree.getroot()
59    results = []
60    data_services = root.findall(EDMX + "DataServices")
61    for ds in data_services:
62        for element in ds:
63            if element.tag == EDM + "Schema":
64                results.extend(parse_schema(element, filename))
65
66    return results
67
68
69def camel_to_snake(name):
70    # snake casing PCIeDevice and PCIeFunction results in mediocre results
71    # given that the standard didn't camel case those in a way that the
72    # algorithm expects, so change the casing explicitly to generate sane
73    # snake case results.
74    name = name.replace("PCIe", "Pcie")
75    name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
76    return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
77
78
79def write_enum_list(redfish_defs_file, enum_list, snake_case_namespace):
80    redfish_defs_file.write(
81        "// SPDX-License-Identifier: Apache-2.0\n"
82        "// SPDX-FileCopyrightText: Copyright OpenBMC Authors\n"
83        "#pragma once\n"
84        "#include <nlohmann/json.hpp>\n\n"
85        "namespace {}\n"
86        "{{\n"
87        "// clang-format off\n\n".format(snake_case_namespace)
88    )
89
90    for element in enum_list:
91        redfish_defs_file.write("enum class {}{{\n".format(element.name))
92        values = element.values
93        if "Invalid" not in values:
94            values.insert(0, "Invalid")
95
96        for value in values:
97            # If the value is numeric, prefix it with the enum name
98            if value.isdigit():
99                enum_value = f"{element.name}{value}"
100            else:
101                enum_value = re.sub(r"[^0-9_a-zA-Z]", "", value)
102
103            redfish_defs_file.write("    {},\n".format(enum_value))
104        redfish_defs_file.write("};\n\n")
105
106    for element in enum_list:
107        values = element.values
108        if "Invalid" not in values:
109            values.insert(0, "Invalid")
110        # nlohmann::json apparently uses c style arrays in their enum
111        # implementation, and clang-tidy isn't smart enough to figure out that
112        # the C arrays are in their code not bmcwebs, so we have to explicitly
113        # ignore the error.
114        redfish_defs_file.write(
115            "NLOHMANN_JSON_SERIALIZE_ENUM({}, {{\n".format(element.name)
116        )
117        for value in values:
118            # If the value is numeric, prefix it with the enum name
119            if value.isdigit():
120                enum_value = f"{element.name}{value}"
121            else:
122                enum_value = re.sub(r"[^0-9_a-zA-Z]", "", value)
123            redfish_defs_file.write(
124                '    {{{}::{}, "{}"}},\n'.format(
125                    element.name, enum_value, value
126                )
127            )
128
129        redfish_defs_file.write("});\n\n")
130
131        print(element.name)
132
133    redfish_defs_file.write("}\n// clang-format on\n")
134
135
136def generate_enums(flat_list):
137    # clear out the old results if they exist
138    if os.path.exists(OUTFOLDER):
139        shutil.rmtree(OUTFOLDER)
140    os.makedirs(OUTFOLDER)
141
142    enum_by_namespace = defaultdict(list)
143
144    for element in flat_list:
145        if isinstance(element, Enum):
146            namespace_split = element.namespace.split(".")[0]
147            enum_by_namespace[namespace_split].append(element)
148
149    for namespace, enum_list in enum_by_namespace.items():
150        snake_case_namespace = camel_to_snake(namespace)
151        outfile = os.path.join(
152            OUTFOLDER, "{}.hpp".format(snake_case_namespace)
153        )
154
155        with open(outfile, "w") as redfish_defs:
156            write_enum_list(redfish_defs, enum_list, snake_case_namespace)
157
158
159def main():
160    print("Reading from {}".format(REDFISH_SCHEMA_DIR))
161
162    filepaths = []
163    for root, dirs, files in os.walk(REDFISH_SCHEMA_DIR):
164        for csdl_file in files:
165            filepath = os.path.join(root, csdl_file)
166            if os.path.islink(filepath):
167                continue
168            if csdl_file.endswith(".xml"):
169                filepaths.append(filepath)
170    print(filepaths)
171    enum_list = []
172    filepaths.sort()
173    for filepath in filepaths:
174        out = parse_file(filepath)
175        enum_list.extend(out)
176
177    print("Parsing done")
178
179    generate_enums(enum_list)
180
181
182if __name__ == "__main__":
183    main()
184