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