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