xref: /openbmc/bmcweb/scripts/generate_schema_enums.py (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
10ec8b83dSEd Tanous#!/usr/bin/python3
20ec8b83dSEd Tanousimport os
3fd06b304SPatrick Williamsimport re
4fd06b304SPatrick Williamsimport shutil
50ec8b83dSEd Tanousimport xml.etree.ElementTree as ET
60ec8b83dSEd Tanousfrom collections import defaultdict
70ec8b83dSEd Tanous
80ec8b83dSEd TanousSCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
90ec8b83dSEd TanousREDFISH_SCHEMA_DIR = os.path.realpath(
10720c9898SEd Tanous    os.path.join(SCRIPT_DIR, "..", "redfish-core", "schema")
110ec8b83dSEd Tanous)
120ec8b83dSEd Tanous
13fd06b304SPatrick WilliamsOUTFOLDER = os.path.realpath(
14fd06b304SPatrick Williams    os.path.join(
15fd06b304SPatrick Williams        SCRIPT_DIR, "..", "redfish-core", "include", "generated", "enums"
160ec8b83dSEd Tanous    )
170ec8b83dSEd Tanous)
180ec8b83dSEd Tanous
190ec8b83dSEd Tanous# Odata string types
200ec8b83dSEd TanousEDMX = "{http://docs.oasis-open.org/odata/ns/edmx}"
210ec8b83dSEd TanousEDM = "{http://docs.oasis-open.org/odata/ns/edm}"
220ec8b83dSEd Tanous
230ec8b83dSEd Tanous
240ec8b83dSEd Tanousclass Enum:
250ec8b83dSEd Tanous    def __init__(self, name, values, namespace, from_file):
260ec8b83dSEd Tanous        self.name = name
270ec8b83dSEd Tanous        self.values = values
280ec8b83dSEd Tanous        self.namespace = namespace
290ec8b83dSEd Tanous        self.from_file = from_file
300ec8b83dSEd Tanous
310ec8b83dSEd Tanous
320ec8b83dSEd Tanousdef parse_schema(element, filename):
330ec8b83dSEd Tanous    EntityTypes = []
340ec8b83dSEd Tanous    namespace = element.attrib["Namespace"]
350ec8b83dSEd Tanous    for schema_element in element:
360ec8b83dSEd Tanous        name = schema_element.attrib.get("Name", None)
370ec8b83dSEd Tanous        if name is None:
380ec8b83dSEd Tanous            continue
390ec8b83dSEd Tanous        if schema_element.tag == EDM + "EnumType":
400ec8b83dSEd Tanous            enums = []
410ec8b83dSEd Tanous            for member in schema_element.findall(EDM + "Member"):
420ec8b83dSEd Tanous                enums.append(member.attrib["Name"])
430ec8b83dSEd Tanous            EntityTypes.append(Enum(name, enums, namespace, filename))
440ec8b83dSEd Tanous    return EntityTypes
450ec8b83dSEd Tanous
460ec8b83dSEd Tanous
470ec8b83dSEd Tanousdef parse_file(filename):
48720c9898SEd Tanous    print(f"Parsing {filename}")
490ec8b83dSEd Tanous    tree = ET.parse(filename)
500ec8b83dSEd Tanous    root = tree.getroot()
510ec8b83dSEd Tanous    results = []
520ec8b83dSEd Tanous    data_services = root.findall(EDMX + "DataServices")
530ec8b83dSEd Tanous    for ds in data_services:
540ec8b83dSEd Tanous        for element in ds:
550ec8b83dSEd Tanous            if element.tag == EDM + "Schema":
560ec8b83dSEd Tanous                results.extend(parse_schema(element, filename))
570ec8b83dSEd Tanous
580ec8b83dSEd Tanous    return results
590ec8b83dSEd Tanous
600ec8b83dSEd Tanous
610ec8b83dSEd Tanousdef camel_to_snake(name):
620ec8b83dSEd Tanous    # snake casing PCIeDevice and PCIeFunction results in mediocre results
630ec8b83dSEd Tanous    # given that the standard didn't camel case those in a way that the
640ec8b83dSEd Tanous    # algorithm expects, so change the casing explicitly to generate sane
650ec8b83dSEd Tanous    # snake case results.
660ec8b83dSEd Tanous    name = name.replace("PCIe", "Pcie")
670ec8b83dSEd Tanous    name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
680ec8b83dSEd Tanous    return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
690ec8b83dSEd Tanous
700ec8b83dSEd Tanous
710ec8b83dSEd Tanousdef write_enum_list(redfish_defs_file, enum_list, snake_case_namespace):
720ec8b83dSEd Tanous    redfish_defs_file.write(
73*40e9b92eSEd Tanous        "// SPDX-License-Identifier: Apache-2.0\n"
74*40e9b92eSEd Tanous        "// SPDX-FileCopyrightText: Copyright OpenBMC Authors\n"
750ec8b83dSEd Tanous        "#pragma once\n"
760ec8b83dSEd Tanous        "#include <nlohmann/json.hpp>\n\n"
770ec8b83dSEd Tanous        "namespace {}\n"
780ec8b83dSEd Tanous        "{{\n"
790ec8b83dSEd Tanous        "// clang-format off\n\n".format(snake_case_namespace)
800ec8b83dSEd Tanous    )
810ec8b83dSEd Tanous
820ec8b83dSEd Tanous    for element in enum_list:
830ec8b83dSEd Tanous        redfish_defs_file.write("enum class {}{{\n".format(element.name))
840ec8b83dSEd Tanous        values = element.values
850ec8b83dSEd Tanous        if "Invalid" not in values:
860ec8b83dSEd Tanous            values.insert(0, "Invalid")
870ec8b83dSEd Tanous
880ec8b83dSEd Tanous        for value in values:
890ec8b83dSEd Tanous            redfish_defs_file.write("    {},\n".format(value))
900ec8b83dSEd Tanous
910ec8b83dSEd Tanous        redfish_defs_file.write("};\n\n")
920ec8b83dSEd Tanous
930ec8b83dSEd Tanous    for element in enum_list:
940ec8b83dSEd Tanous        values = element.values
950ec8b83dSEd Tanous        if "Invalid" not in values:
960ec8b83dSEd Tanous            values.insert(0, "Invalid")
978ece0e45SEd Tanous        # nlohmann::json apparently uses c style arrays in their enum
980ec8b83dSEd Tanous        # implementation, and clang-tidy isn't smart enough to figure out that
990ec8b83dSEd Tanous        # the C arrays are in their code not bmcwebs, so we have to explicitly
1000ec8b83dSEd Tanous        # ignore the error.
1010ec8b83dSEd Tanous        redfish_defs_file.write(
102fd06b304SPatrick Williams            "NLOHMANN_JSON_SERIALIZE_ENUM({}, {{\n".format(element.name)
1030ec8b83dSEd Tanous        )
1040ec8b83dSEd Tanous        for value in values:
1050ec8b83dSEd Tanous            redfish_defs_file.write(
1060ec8b83dSEd Tanous                '    {{{}::{}, "{}"}},\n'.format(element.name, value, value)
1070ec8b83dSEd Tanous            )
1080ec8b83dSEd Tanous
1090ec8b83dSEd Tanous        redfish_defs_file.write("});\n\n")
1100ec8b83dSEd Tanous
1110ec8b83dSEd Tanous        print(element.name)
1120ec8b83dSEd Tanous
113fd06b304SPatrick Williams    redfish_defs_file.write("}\n// clang-format on\n")
1140ec8b83dSEd Tanous
1150ec8b83dSEd Tanous
1160ec8b83dSEd Tanousdef generate_enums(flat_list):
1170ec8b83dSEd Tanous    # clear out the old results if they exist
1180ec8b83dSEd Tanous    if os.path.exists(OUTFOLDER):
1190ec8b83dSEd Tanous        shutil.rmtree(OUTFOLDER)
1200ec8b83dSEd Tanous    os.makedirs(OUTFOLDER)
1210ec8b83dSEd Tanous
1220ec8b83dSEd Tanous    enum_by_namespace = defaultdict(list)
1230ec8b83dSEd Tanous
1240ec8b83dSEd Tanous    for element in flat_list:
1250ec8b83dSEd Tanous        if isinstance(element, Enum):
1260ec8b83dSEd Tanous            namespace_split = element.namespace.split(".")[0]
1270ec8b83dSEd Tanous            enum_by_namespace[namespace_split].append(element)
1280ec8b83dSEd Tanous
1290ec8b83dSEd Tanous    for namespace, enum_list in enum_by_namespace.items():
1300ec8b83dSEd Tanous        snake_case_namespace = camel_to_snake(namespace)
1310ec8b83dSEd Tanous        outfile = os.path.join(
1320ec8b83dSEd Tanous            OUTFOLDER, "{}.hpp".format(snake_case_namespace)
1330ec8b83dSEd Tanous        )
1340ec8b83dSEd Tanous
1350ec8b83dSEd Tanous        with open(outfile, "w") as redfish_defs:
1360ec8b83dSEd Tanous            write_enum_list(redfish_defs, enum_list, snake_case_namespace)
1370ec8b83dSEd Tanous
1380ec8b83dSEd Tanous
1390ec8b83dSEd Tanousdef main():
1400ec8b83dSEd Tanous    print("Reading from {}".format(REDFISH_SCHEMA_DIR))
1410ec8b83dSEd Tanous
142720c9898SEd Tanous    filepaths = []
143720c9898SEd Tanous    for root, dirs, files in os.walk(REDFISH_SCHEMA_DIR):
144720c9898SEd Tanous        for csdl_file in files:
145a529a6aaSEd Tanous            filepath = os.path.join(root, csdl_file)
146a529a6aaSEd Tanous            if os.path.islink(filepath):
147a529a6aaSEd Tanous                continue
148720c9898SEd Tanous            if csdl_file.endswith(".xml"):
149a529a6aaSEd Tanous                filepaths.append(filepath)
150720c9898SEd Tanous    print(filepaths)
1510ec8b83dSEd Tanous    enum_list = []
152a529a6aaSEd Tanous    filepaths.sort()
1530ec8b83dSEd Tanous    for filepath in filepaths:
1540ec8b83dSEd Tanous        out = parse_file(filepath)
1550ec8b83dSEd Tanous        enum_list.extend(out)
1560ec8b83dSEd Tanous
1570ec8b83dSEd Tanous    print("Parsing done")
1580ec8b83dSEd Tanous
1590ec8b83dSEd Tanous    generate_enums(enum_list)
1600ec8b83dSEd Tanous
1610ec8b83dSEd Tanous
1620ec8b83dSEd Tanousif __name__ == "__main__":
1630ec8b83dSEd Tanous    main()
164