xref: /openbmc/bmcweb/scripts/generate_schema_enums.py (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
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    return EntityTypes
45
46
47def parse_file(filename):
48    print(f"Parsing {filename}")
49    tree = ET.parse(filename)
50    root = tree.getroot()
51    results = []
52    data_services = root.findall(EDMX + "DataServices")
53    for ds in data_services:
54        for element in ds:
55            if element.tag == EDM + "Schema":
56                results.extend(parse_schema(element, filename))
57
58    return results
59
60
61def camel_to_snake(name):
62    # snake casing PCIeDevice and PCIeFunction results in mediocre results
63    # given that the standard didn't camel case those in a way that the
64    # algorithm expects, so change the casing explicitly to generate sane
65    # snake case results.
66    name = name.replace("PCIe", "Pcie")
67    name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
68    return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
69
70
71def write_enum_list(redfish_defs_file, enum_list, snake_case_namespace):
72    redfish_defs_file.write(
73        "// SPDX-License-Identifier: Apache-2.0\n"
74        "// SPDX-FileCopyrightText: Copyright OpenBMC Authors\n"
75        "#pragma once\n"
76        "#include <nlohmann/json.hpp>\n\n"
77        "namespace {}\n"
78        "{{\n"
79        "// clang-format off\n\n".format(snake_case_namespace)
80    )
81
82    for element in enum_list:
83        redfish_defs_file.write("enum class {}{{\n".format(element.name))
84        values = element.values
85        if "Invalid" not in values:
86            values.insert(0, "Invalid")
87
88        for value in values:
89            redfish_defs_file.write("    {},\n".format(value))
90
91        redfish_defs_file.write("};\n\n")
92
93    for element in enum_list:
94        values = element.values
95        if "Invalid" not in values:
96            values.insert(0, "Invalid")
97        # nlohmann::json apparently uses c style arrays in their enum
98        # implementation, and clang-tidy isn't smart enough to figure out that
99        # the C arrays are in their code not bmcwebs, so we have to explicitly
100        # ignore the error.
101        redfish_defs_file.write(
102            "NLOHMANN_JSON_SERIALIZE_ENUM({}, {{\n".format(element.name)
103        )
104        for value in values:
105            redfish_defs_file.write(
106                '    {{{}::{}, "{}"}},\n'.format(element.name, value, value)
107            )
108
109        redfish_defs_file.write("});\n\n")
110
111        print(element.name)
112
113    redfish_defs_file.write("}\n// clang-format on\n")
114
115
116def generate_enums(flat_list):
117    # clear out the old results if they exist
118    if os.path.exists(OUTFOLDER):
119        shutil.rmtree(OUTFOLDER)
120    os.makedirs(OUTFOLDER)
121
122    enum_by_namespace = defaultdict(list)
123
124    for element in flat_list:
125        if isinstance(element, Enum):
126            namespace_split = element.namespace.split(".")[0]
127            enum_by_namespace[namespace_split].append(element)
128
129    for namespace, enum_list in enum_by_namespace.items():
130        snake_case_namespace = camel_to_snake(namespace)
131        outfile = os.path.join(
132            OUTFOLDER, "{}.hpp".format(snake_case_namespace)
133        )
134
135        with open(outfile, "w") as redfish_defs:
136            write_enum_list(redfish_defs, enum_list, snake_case_namespace)
137
138
139def main():
140    print("Reading from {}".format(REDFISH_SCHEMA_DIR))
141
142    filepaths = []
143    for root, dirs, files in os.walk(REDFISH_SCHEMA_DIR):
144        for csdl_file in files:
145            filepath = os.path.join(root, csdl_file)
146            if os.path.islink(filepath):
147                continue
148            if csdl_file.endswith(".xml"):
149                filepaths.append(filepath)
150    print(filepaths)
151    enum_list = []
152    filepaths.sort()
153    for filepath in filepaths:
154        out = parse_file(filepath)
155        enum_list.extend(out)
156
157    print("Parsing done")
158
159    generate_enums(enum_list)
160
161
162if __name__ == "__main__":
163    main()
164