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        "#pragma once\n"
74        "#include <nlohmann/json.hpp>\n\n"
75        "namespace {}\n"
76        "{{\n"
77        "// clang-format off\n\n".format(snake_case_namespace)
78    )
79
80    for element in enum_list:
81        redfish_defs_file.write("enum class {}{{\n".format(element.name))
82        values = element.values
83        if "Invalid" not in values:
84            values.insert(0, "Invalid")
85
86        for value in values:
87            redfish_defs_file.write("    {},\n".format(value))
88
89        redfish_defs_file.write("};\n\n")
90
91    for element in enum_list:
92        values = element.values
93        if "Invalid" not in values:
94            values.insert(0, "Invalid")
95        # nlohmann::json apparently uses c style arrays in their enum
96        # implementation, and clang-tidy isn't smart enough to figure out that
97        # the C arrays are in their code not bmcwebs, so we have to explicitly
98        # ignore the error.
99        redfish_defs_file.write(
100            "NLOHMANN_JSON_SERIALIZE_ENUM({}, {{\n".format(element.name)
101        )
102        for value in values:
103            redfish_defs_file.write(
104                '    {{{}::{}, "{}"}},\n'.format(element.name, value, value)
105            )
106
107        redfish_defs_file.write("});\n\n")
108
109        print(element.name)
110
111    redfish_defs_file.write("}\n// clang-format on\n")
112
113
114def generate_enums(flat_list):
115    # clear out the old results if they exist
116    if os.path.exists(OUTFOLDER):
117        shutil.rmtree(OUTFOLDER)
118    os.makedirs(OUTFOLDER)
119
120    enum_by_namespace = defaultdict(list)
121
122    for element in flat_list:
123        if isinstance(element, Enum):
124            namespace_split = element.namespace.split(".")[0]
125            enum_by_namespace[namespace_split].append(element)
126
127    for namespace, enum_list in enum_by_namespace.items():
128        snake_case_namespace = camel_to_snake(namespace)
129        outfile = os.path.join(
130            OUTFOLDER, "{}.hpp".format(snake_case_namespace)
131        )
132
133        with open(outfile, "w") as redfish_defs:
134            write_enum_list(redfish_defs, enum_list, snake_case_namespace)
135
136
137def main():
138    print("Reading from {}".format(REDFISH_SCHEMA_DIR))
139
140    filepaths = []
141    for root, dirs, files in os.walk(REDFISH_SCHEMA_DIR):
142        for csdl_file in files:
143            filepath = os.path.join(root, csdl_file)
144            if os.path.islink(filepath):
145                continue
146            if csdl_file.endswith(".xml"):
147                filepaths.append(filepath)
148    print(filepaths)
149    enum_list = []
150    filepaths.sort()
151    for filepath in filepaths:
152        out = parse_file(filepath)
153        enum_list.extend(out)
154
155    print("Parsing done")
156
157    generate_enums(enum_list)
158
159
160if __name__ == "__main__":
161    main()
162