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