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): 90 if self.defaultValue is not None: 91 return " = " + str(self.defaultValue) 92 else: 93 return "" 94 95 def __cppTypeParam( 96 self, interface, cppTypeName, full=False, typename="common" 97 ): 98 iface = NamedElement(name=interface).cppNamespacedClass() 99 r = cppTypeName 100 101 # Fix up local enum placeholders. 102 if full: 103 r = r.replace(self.LOCAL_ENUM_MAGIC, iface) 104 else: 105 r = r.replace(self.LOCAL_ENUM_MAGIC + "::", "") 106 107 # Fix up non-local enum placeholders. 108 r = r.replace(self.NONLOCAL_ENUM_MAGIC, typename) 109 110 return r 111 112 """ Determine the C++ header of an enumeration-type property. 113 """ 114 115 def enum_headers(self, interface): 116 typeTuple = self.__type_tuple() 117 return self.__enum_headers(interface, typeTuple) 118 119 def __enum_headers(self, interface, typeTuple): 120 # Enums can be processed directly. 121 if "enum" == typeTuple[0]: 122 # Get enum type from typeTuple. 123 enumType = typeTuple[1][0][0] 124 125 # Local enums don't need a header. 126 if "self." in enumType: 127 return [] 128 129 enumType = ".".join(enumType.split(".")[0:-1]) 130 return [NamedElement(name=enumType).headerFile()] 131 132 # If the details part of the tuple has zero entries, no enums are 133 # present 134 if len(typeTuple[1]) == 0: 135 return [] 136 137 # Otherwise, the tuple-type might have enums embedded in it. Handle 138 # them recursively. 139 r = [] 140 for t in typeTuple[1]: 141 r.extend(self.__enum_headers(interface, t)) 142 return r 143 144 """ Convert the property type into a C++ type. 145 """ 146 147 def parse_cpp_type(self): 148 if not self.typeName: 149 return None 150 151 typeTuple = self.__type_tuple() 152 return self.__parse_cpp_type__(typeTuple) 153 154 """ Convert the 'typeName' into a tuple of ('type', [ details ]) where 155 'details' is a recursive type-tuple. 156 """ 157 158 def __type_tuple(self): 159 if not self.typeName: 160 return None 161 162 """ typeNames are _almost_ valid YAML. Insert a , before each [ 163 and then wrap it in a [ ] and it becomes valid YAML. (assuming 164 the user gave us a valid typename) 165 """ 166 typeName = self.typeName 167 typeArray = yaml.safe_load("[" + ",[".join(typeName.split("[")) + "]") 168 return self.__preprocess_yaml_type_array(typeArray).pop(0) 169 170 """ Take a list of dbus types from YAML and convert it to a recursive data 171 structure. Each entry of the original list gets converted into a tuple 172 consisting of the type name and a list with the params for this type, 173 e.g. 174 ['dict', ['string', 'dict', ['string', 'int64']]] 175 is converted to 176 [('dict', [('string', []), ('dict', [('string', []), 177 ('int64', [])]]] 178 """ 179 180 def __preprocess_yaml_type_array(self, typeArray): 181 result = [] 182 183 for i in range(len(typeArray)): 184 # Ignore lists because we merge them with the previous element 185 if type(typeArray[i]) is list: 186 continue 187 188 # If there is a next element and it is a list, merge it with the 189 # current element. 190 if i < len(typeArray) - 1 and type(typeArray[i + 1]) is list: 191 result.append( 192 ( 193 typeArray[i], 194 self.__preprocess_yaml_type_array(typeArray[i + 1]), 195 ) 196 ) 197 else: 198 result.append((typeArray[i], [])) 199 200 return result 201 202 """ Take a list of dbus types and perform validity checking, such as: 203 [ variant [ dict [ int32, int32 ], double ] ] 204 This function then converts the type-list into a C++ type string. 205 """ 206 207 def __parse_cpp_type__(self, typeTuple): 208 propertyMap = { 209 "byte": {"cppName": "uint8_t", "params": 0}, 210 "boolean": {"cppName": "bool", "params": 0}, 211 "int16": {"cppName": "int16_t", "params": 0}, 212 "uint16": {"cppName": "uint16_t", "params": 0}, 213 "int32": {"cppName": "int32_t", "params": 0}, 214 "uint32": {"cppName": "uint32_t", "params": 0}, 215 "int64": {"cppName": "int64_t", "params": 0}, 216 "uint64": {"cppName": "uint64_t", "params": 0}, 217 "size": {"cppName": "size_t", "params": 0}, 218 "ssize": {"cppName": "ssize_t", "params": 0}, 219 "double": {"cppName": "double", "params": 0}, 220 "unixfd": {"cppName": "sdbusplus::message::unix_fd", "params": 0}, 221 "string": {"cppName": "std::string", "params": 0}, 222 "object_path": { 223 "cppName": "sdbusplus::message::object_path", 224 "params": 0, 225 }, 226 "signature": { 227 "cppName": "sdbusplus::message::signature", 228 "params": 0, 229 }, 230 "array": {"cppName": "std::vector", "params": 1}, 231 "set": {"cppName": "std::set", "params": 1}, 232 "struct": {"cppName": "std::tuple", "params": -1}, 233 "variant": {"cppName": "std::variant", "params": -1}, 234 "dict": {"cppName": "std::map", "params": 2}, 235 "enum": {"cppName": "enum", "params": 1}, 236 } 237 238 if len(typeTuple) != 2: 239 raise RuntimeError("Invalid typeTuple %s" % typeTuple) 240 241 first = typeTuple[0] 242 entry = propertyMap[first] 243 244 result = entry["cppName"] 245 246 # Handle 0-entry parameter lists. 247 if entry["params"] == 0: 248 if len(typeTuple[1]) != 0: 249 raise RuntimeError("Invalid typeTuple %s" % typeTuple) 250 else: 251 return result 252 253 # Get the parameter list 254 rest = typeTuple[1] 255 256 # Confirm parameter count matches. 257 if (entry["params"] != -1) and (entry["params"] != len(rest)): 258 raise RuntimeError( 259 "Invalid entry count for %s : %s" % (first, rest) 260 ) 261 262 # Switch enum for proper type. 263 if result == "enum": 264 result = rest[0][0] 265 266 # self. means local type. 267 if result.startswith("self."): 268 return result.replace("self.", self.LOCAL_ENUM_MAGIC + "::") 269 270 # Insert place-holder for header-type namespace (ex. "common") 271 result = result.split(".") 272 result = "::".join( 273 [ 274 "sdbusplus", 275 self.NONLOCAL_ENUM_MAGIC, 276 NamedElement( 277 name=".".join(result[0:-1]) 278 ).cppNamespacedClass(), 279 NamedElement(name=result[-1]).classname, 280 ] 281 ) 282 return result 283 284 # Parse each parameter entry, if appropriate, and create C++ template 285 # syntax. 286 result += "<" 287 result += ", ".join([self.__parse_cpp_type__(e) for e in rest]) 288 result += ">" 289 290 return result 291 292 def markdown(self, loader): 293 return self.render( 294 loader, "property.md.mako", property=self, post=str.strip 295 ) 296 297 def cpp_includes(self, interface): 298 return interface.error_includes(self.errors) + interface.enum_includes( 299 [self] 300 ) 301 302 def or_cpp_flags(self, flags): 303 """Return the corresponding ORed cpp flags.""" 304 flags_dict = { 305 "const": "vtable::property_::const_", 306 "deprecated": "vtable::common_::deprecated", 307 "emits_change": "vtable::property_::emits_change", 308 "emits_invalidation": "vtable::property_::emits_invalidation", 309 "explicit": "vtable::property_::explicit_", 310 "hidden": "vtable::common_::hidden", 311 "readonly": False, 312 "unprivileged": "vtable::common_::unprivileged", 313 } 314 315 cpp_flags = [] 316 for flag in flags: 317 try: 318 if flags_dict[flag]: 319 cpp_flags.append(flags_dict[flag]) 320 except KeyError: 321 raise ValueError('Invalid flag "{}"'.format(flag)) 322 323 return " | ".join(cpp_flags) 324