1import yaml 2 3from .namedelement import NamedElement 4from .renderer import Renderer 5 6 7class Property(NamedElement, Renderer): 8 LOCAL_ENUM_MAGIC = "<LOCAL_ENUM>" 9 NONLOCAL_ENUM_MAGIC = "<NONLOCAL_ENUM>" 10 11 def __init__(self, **kwargs): 12 self.typeName = kwargs.pop("type", None) 13 self.cppTypeName = self.parse_cpp_type() 14 self.defaultValue = kwargs.pop("default", None) 15 self.flags = kwargs.pop("flags", []) 16 self.cpp_flags = self.or_cpp_flags(self.flags) 17 self.errors = kwargs.pop("errors", []) 18 19 if self.defaultValue is not None: 20 if isinstance(self.defaultValue, bool): 21 # Convert True/False to 'true'/'false' 22 # because it will be rendered as C++ code 23 self.defaultValue = "true" if self.defaultValue else "false" 24 elif ( 25 isinstance(self.defaultValue, str) 26 and self.typeName.lower() == "string" 27 ): 28 # Wrap string type default values with double-quotes 29 self.defaultValue = '"' + self.defaultValue + '"' 30 elif ( 31 isinstance(self.defaultValue, str) and self.is_floating_point() 32 ): 33 if self.defaultValue.lower() == "nan": 34 self.defaultValue = ( 35 f"std::numeric_limits<{self.cppTypeName}>::quiet_NaN()" 36 ) 37 elif self.defaultValue.lower() == "infinity": 38 self.defaultValue = ( 39 f"std::numeric_limits<{self.cppTypeName}>::infinity()" 40 ) 41 elif self.defaultValue.lower() == "-infinity": 42 self.defaultValue = ( 43 f"-std::numeric_limits<{self.cppTypeName}>::infinity()" 44 ) 45 elif self.defaultValue.lower() == "epsilon": 46 self.defaultValue = ( 47 f"std::numeric_limits<{self.cppTypeName}>::epsilon()" 48 ) 49 elif isinstance(self.defaultValue, str) and self.is_integer(): 50 if self.defaultValue.lower() == "maxint": 51 self.defaultValue = ( 52 f"std::numeric_limits<{self.cppTypeName}>::max()" 53 ) 54 elif self.defaultValue.lower() == "minint": 55 self.defaultValue = ( 56 f"std::numeric_limits<{self.cppTypeName}>::min()" 57 ) 58 59 super(Property, self).__init__(**kwargs) 60 61 def is_enum(self): 62 if not self.typeName: 63 return False 64 return "enum" == self.__type_tuple()[0] 65 66 def is_integer(self): 67 return self.typeName in [ 68 "byte", 69 "int16", 70 "uint16", 71 "int32", 72 "uint32", 73 "int64", 74 "uint64", 75 "size", 76 "ssize", 77 ] 78 79 def is_floating_point(self): 80 return self.typeName in ["double"] 81 82 """ Return a conversion of the cppTypeName valid as a function parameter. 83 Currently only 'enum' requires conversion. 84 """ 85 86 def cppTypeParam(self, interface, full=False, typename="common"): 87 return self.__cppTypeParam(interface, self.cppTypeName, full, typename) 88 89 def default_value(self, interface): 90 if self.defaultValue is None: 91 return "" 92 value = str(self.defaultValue) 93 enum_prefix = "" 94 if self.is_enum(): 95 p_type = self.cppTypeParam(interface) 96 enum_prefix = f"{p_type}::" 97 return f" = {enum_prefix}{value}" 98 99 def __cppTypeParam( 100 self, interface, cppTypeName, full=False, typename="common" 101 ): 102 iface = NamedElement(name=interface).cppNamespacedClass() 103 r = cppTypeName 104 105 # Fix up local enum placeholders. 106 if full: 107 r = r.replace(self.LOCAL_ENUM_MAGIC, iface) 108 else: 109 r = r.replace(self.LOCAL_ENUM_MAGIC + "::", "") 110 111 # Fix up non-local enum placeholders. 112 r = r.replace(self.NONLOCAL_ENUM_MAGIC, typename) 113 114 return r 115 116 """ Determine the C++ header of an enumeration-type property. 117 """ 118 119 def enum_headers(self, interface=None): 120 typeTuple = self.__type_tuple() 121 return self.__enum_headers(typeTuple, interface) 122 123 def __enum_headers(self, typeTuple, interface=None): 124 # Enums can be processed directly. 125 if "enum" == typeTuple[0]: 126 # Get enum type from typeTuple. 127 enumType = typeTuple[1][0][0] 128 129 # Local enums don't need a header, unless interface is provided. 130 if "self." in enumType: 131 if interface: 132 enumType = enumType.replace("self.", interface + ".") 133 else: 134 return [] 135 136 enumType = ".".join(enumType.split(".")[0:-1]) 137 return [NamedElement(name=enumType).headerFile()] 138 139 # If the details part of the tuple has zero entries, no enums are 140 # present 141 if len(typeTuple[1]) == 0: 142 return [] 143 144 # Otherwise, the tuple-type might have enums embedded in it. Handle 145 # them recursively. 146 r = [] 147 for t in typeTuple[1]: 148 r.extend(self.__enum_headers(t, interface)) 149 return r 150 151 """ Convert the property type into a C++ type. 152 """ 153 154 def parse_cpp_type(self): 155 if not self.typeName: 156 return None 157 158 typeTuple = self.__type_tuple() 159 return self.__parse_cpp_type__(typeTuple) 160 161 """ Convert the 'typeName' into a tuple of ('type', [ details ]) where 162 'details' is a recursive type-tuple. 163 """ 164 165 def __type_tuple(self): 166 if not self.typeName: 167 return None 168 169 """ typeNames are _almost_ valid YAML. Insert a , before each [ 170 and then wrap it in a [ ] and it becomes valid YAML. (assuming 171 the user gave us a valid typename) 172 """ 173 typeName = self.typeName 174 typeArray = yaml.safe_load("[" + ",[".join(typeName.split("[")) + "]") 175 return self.__preprocess_yaml_type_array(typeArray).pop(0) 176 177 """ Take a list of dbus types from YAML and convert it to a recursive data 178 structure. Each entry of the original list gets converted into a tuple 179 consisting of the type name and a list with the params for this type, 180 e.g. 181 ['dict', ['string', 'dict', ['string', 'int64']]] 182 is converted to 183 [('dict', [('string', []), ('dict', [('string', []), 184 ('int64', [])]]] 185 """ 186 187 def __preprocess_yaml_type_array(self, typeArray): 188 result = [] 189 190 for i in range(len(typeArray)): 191 # Ignore lists because we merge them with the previous element 192 if type(typeArray[i]) is list: 193 continue 194 195 # If there is a next element and it is a list, merge it with the 196 # current element. 197 if i < len(typeArray) - 1 and type(typeArray[i + 1]) is list: 198 result.append( 199 ( 200 typeArray[i], 201 self.__preprocess_yaml_type_array(typeArray[i + 1]), 202 ) 203 ) 204 else: 205 result.append((typeArray[i], [])) 206 207 return result 208 209 propertyMap = { 210 "byte": {"cppName": "uint8_t", "params": 0}, 211 "boolean": {"cppName": "bool", "params": 0}, 212 "int16": {"cppName": "int16_t", "params": 0}, 213 "uint16": {"cppName": "uint16_t", "params": 0}, 214 "int32": {"cppName": "int32_t", "params": 0}, 215 "uint32": {"cppName": "uint32_t", "params": 0}, 216 "int64": { 217 "cppName": "int64_t", 218 "params": 0, 219 "registryName": "number", 220 }, 221 "uint64": { 222 "cppName": "uint64_t", 223 "params": 0, 224 "registryName": "number", 225 }, 226 "size": { 227 "cppName": "size_t", 228 "params": 0, 229 "registryName": "number", 230 }, 231 "ssize": {"cppName": "ssize_t", "params": 0}, 232 "double": { 233 "cppName": "double", 234 "params": 0, 235 "registryName": "number", 236 }, 237 "unixfd": {"cppName": "sdbusplus::message::unix_fd", "params": 0}, 238 "string": { 239 "cppName": "std::string", 240 "params": 0, 241 "registryName": "string", 242 }, 243 "object_path": { 244 "cppName": "sdbusplus::message::object_path", 245 "params": 0, 246 "registryName": "string", 247 }, 248 "signature": { 249 "cppName": "sdbusplus::message::signature", 250 "params": 0, 251 }, 252 "array": {"cppName": "std::vector", "params": 1}, 253 "set": {"cppName": "std::set", "params": 1}, 254 "struct": {"cppName": "std::tuple", "params": -1}, 255 "variant": {"cppName": "std::variant", "params": -1}, 256 "dict": {"cppName": "std::map", "params": 2}, 257 "enum": { 258 "cppName": "enum", 259 "params": 1, 260 "registryName": "number", 261 }, 262 } 263 264 """ Get the registry type of the property. """ 265 266 def registry_type(self): 267 return Property.propertyMap[self.typeName]["registryName"] 268 269 """ Take a list of dbus types and perform validity checking, such as: 270 [ variant [ dict [ int32, int32 ], double ] ] 271 This function then converts the type-list into a C++ type string. 272 """ 273 274 def __parse_cpp_type__(self, typeTuple): 275 if len(typeTuple) != 2: 276 raise RuntimeError("Invalid typeTuple %s" % typeTuple) 277 278 first = typeTuple[0] 279 entry = Property.propertyMap[first] 280 281 result = entry["cppName"] 282 283 # Handle 0-entry parameter lists. 284 if entry["params"] == 0: 285 if len(typeTuple[1]) != 0: 286 raise RuntimeError("Invalid typeTuple %s" % typeTuple) 287 else: 288 return result 289 290 # Get the parameter list 291 rest = typeTuple[1] 292 293 # Confirm parameter count matches. 294 if (entry["params"] != -1) and (entry["params"] != len(rest)): 295 raise RuntimeError( 296 "Invalid entry count for %s : %s" % (first, rest) 297 ) 298 299 # Switch enum for proper type. 300 if result == "enum": 301 result = rest[0][0] 302 303 # self. means local type. 304 if result.startswith("self."): 305 return result.replace("self.", self.LOCAL_ENUM_MAGIC + "::") 306 307 # Insert place-holder for header-type namespace (ex. "common") 308 result = result.split(".") 309 result = "::".join( 310 [ 311 "sdbusplus", 312 self.NONLOCAL_ENUM_MAGIC, 313 NamedElement( 314 name=".".join(result[0:-1]) 315 ).cppNamespacedClass(), 316 NamedElement(name=result[-1]).classname, 317 ] 318 ) 319 return result 320 321 # Parse each parameter entry, if appropriate, and create C++ template 322 # syntax. 323 result += "<" 324 result += ", ".join([self.__parse_cpp_type__(e) for e in rest]) 325 result += ">" 326 327 return result 328 329 def markdown(self, loader): 330 return self.render( 331 loader, "property.md.mako", property=self, post=str.strip 332 ) 333 334 def cpp_includes(self, interface): 335 return interface.error_includes(self.errors) + interface.enum_includes( 336 [self] 337 ) 338 339 def or_cpp_flags(self, flags): 340 """Return the corresponding ORed cpp flags.""" 341 flags_dict = { 342 "const": "vtable::property_::const_", 343 "deprecated": "vtable::common_::deprecated", 344 "emits_change": "vtable::property_::emits_change", 345 "emits_invalidation": "vtable::property_::emits_invalidation", 346 "explicit": "vtable::property_::explicit_", 347 "hidden": "vtable::common_::hidden", 348 "readonly": False, 349 "unprivileged": "vtable::common_::unprivileged", 350 } 351 352 cpp_flags = [] 353 for flag in flags: 354 try: 355 if flags_dict[flag]: 356 cpp_flags.append(flags_dict[flag]) 357 except KeyError: 358 raise ValueError('Invalid flag "{}"'.format(flag)) 359 360 return " | ".join(cpp_flags) 361