File indexing completed on 2024-06-23 05:16:56

0001 # SPDX-FileCopyrightText: 2021 Daniel Vrátil <dvratil@kde.org>
0002 #
0003 # SPDX-License-Identifier: LGPL-2.1-only
0004 # SPDX-License-Identifier: LGPL-3.0-only
0005 # SPDX-License-Identifier: LicenseRef-KDE-Accepted-LGPL
0006 
0007 from typing import Dict, List, Optional, cast
0008 
0009 class Type:
0010     """
0011     Base class for all representation of schema types.
0012     """
0013 
0014     _is_array = False
0015     _is_ref = False
0016     _is_object = False
0017     _is_enum = False
0018     _is_builtin = False
0019 
0020     def __init__(self, name):
0021         self._name = name
0022 
0023     def __lt__(self, other: "Type") -> bool:
0024         # pylint: disable=no-member
0025         if (isinstance(self, BuiltinType) and cast(BuiltinType, self).is_qt_type) and (
0026             not isinstance(other, BuiltinType)
0027             or not cast(BuiltinType, other).is_qt_type
0028         ):
0029             return True
0030         if isinstance(self, BuiltinType) and not isinstance(other, BuiltinType):
0031             return True
0032         return self._name < other._name
0033 
0034     def rename_to(self, new_name: str) -> None:
0035         self._name = new_name
0036 
0037     @classmethod
0038     @property
0039     def is_array(cls) -> bool:
0040         return cls._is_array
0041 
0042     @classmethod
0043     @property
0044     def is_ref(cls) -> bool:
0045         return cls._is_ref
0046 
0047     @classmethod
0048     @property
0049     def is_object(cls) -> bool:
0050         return cls._is_object
0051 
0052     @classmethod
0053     @property
0054     def is_enum(cls) -> bool:
0055         return cls._is_enum
0056 
0057     @classmethod
0058     @property
0059     def is_builtin(cls) -> bool:
0060         return cls._is_builtin
0061 
0062     @staticmethod
0063     def _map_to_builtin(name: str, orig_ref: "Ref") -> "Optional[Type]":
0064         if name == "Date":
0065             return QDate(orig_ref=orig_ref)
0066         return None
0067 
0068     @staticmethod
0069     def parse(
0070         type_schema: Dict, name: str, schema_name: str, ref_visitor: "RefVisitor"
0071     ):
0072         # pylint: disable=too-many-return-statements
0073         if "enum" in type_schema:
0074             return Enum(name, schema_name, type_schema)
0075         if "type" in type_schema:
0076             prop_type = type_schema["type"]
0077             if prop_type == "array":
0078                 return QList(type_schema["items"], name, schema_name, ref_visitor)
0079             if prop_type == "boolean":
0080                 return Boolean()
0081             if prop_type == "integer":
0082                 return Integer()
0083             if prop_type == "object":
0084                 if "additionalProperties" in type_schema:
0085                     return QVariantMap()
0086                 return Object(type_schema, ref_visitor)
0087             if prop_type == "string":
0088                 return QString()
0089             raise RuntimeError(f'Unknown type "{prop_type}"')
0090         if "$ref" in type_schema:
0091             builtin = Type._map_to_builtin(
0092                 type_schema["$ref"], Ref(type_schema["$ref"], ref_visitor)
0093             )
0094             return builtin if builtin else Ref(type_schema["$ref"], ref_visitor)
0095 
0096         raise RuntimeError(f"Missing type information in schema {type_schema}.")
0097 
0098     @property
0099     def name(self) -> str:
0100         return self._name
0101 
0102     @property
0103     def full_name(self) -> str:
0104         return self.name
0105 
0106 
0107 class Property:
0108     """
0109     Represents a single property of an Object type.
0110 
0111     A property has a name, and a type, described by instance of a Type subclass.
0112     """
0113 
0114     def __init__(
0115         self, name: str, prop: Dict, schema_name: str, ref_visitor: "RefVisitor"
0116     ):
0117         self._prop = prop
0118         self._name = name
0119         self._orig_name = name
0120         self._description = prop["description"]
0121         self._read_only = prop.get("readOnly", False)
0122         self._type = Type.parse(self._prop, self._name, schema_name, ref_visitor)
0123         # Avoid some C++ reserved words
0124         if self._name == "default":
0125             self._name = "isDefault"
0126 
0127     def __repr__(self) -> str:
0128         return f"{self._name}({self._type.name})"
0129 
0130     @property
0131     def name(self) -> str:
0132         return self._name
0133 
0134     @property
0135     def capitalized_name(self) -> str:
0136         return self._name[0].upper() + self._name[1:]
0137 
0138     @property
0139     def description(self) -> str:
0140         return self._description
0141 
0142     @property
0143     def type(self):
0144         return self._type
0145 
0146     @property
0147     def read_only(self) -> bool:
0148         return self._read_only
0149 
0150     @property
0151     def orig_name(self) -> str:
0152         return self._orig_name
0153 
0154 
0155 class Object(Type):
0156     """
0157     Maps to the "object" type as it appears in the schema documents.
0158     An Object type is composed of multiple properties.
0159     """
0160 
0161     _is_object = True
0162 
0163     def __init__(self, schema: dict, ref_visitor: "RefVisitor"):
0164         super().__init__(schema["id"])
0165         assert schema["type"] == "object"
0166         self._description: str = schema["description"]
0167         self._properties = list(
0168             map(
0169                 lambda tpl: Property(
0170                     tpl[0], tpl[1], schema_name=self._name, ref_visitor=ref_visitor
0171                 ),
0172                 schema["properties"].items(),
0173             )
0174         )
0175 
0176     def __repr__(self) -> str:
0177         return f"<{self._name} {{ {','.join(map(str, self._properties))} }}>"
0178 
0179     @property
0180     def description(self) -> str:
0181         return self._description
0182 
0183     @property
0184     def properties(self) -> List[Property]:
0185         return self._properties
0186 
0187     @property
0188     def nested_enums(self) -> "List[Enum]":
0189         enums = []
0190         for prop in self._properties:
0191             if isinstance(prop.type, Enum):
0192                 enums.append(prop.type)
0193             elif prop.type.is_array and isinstance(prop.type.element_type, Enum):
0194                 enums.append(prop.type.element_type)
0195         return enums
0196 
0197     @property
0198     def dependencies(self) -> List[Type]:
0199         deps = []
0200         known = set()
0201 
0202         def add_dep(dep_type) -> None:
0203             if dep_type.name not in known:
0204                 known.add(dep_type.name)
0205                 deps.append(dep_type)
0206 
0207         for prop in self._properties:
0208             if prop.type.is_array:
0209                 add_dep(prop.type)
0210                 add_dep(prop.type.element_type)
0211             elif prop.type.is_ref or (prop.type.is_builtin and prop.type.is_qt_type):
0212                 add_dep(prop.type)
0213         return sorted(deps)
0214 
0215     @property
0216     def hhhhas_primary(self) -> bool:
0217         for prop in self._properties:
0218             if prop.name == "primary":
0219                 return True
0220         return False
0221 
0222 
0223 class EnumValue:
0224     """
0225     A single value of an enumerator.
0226     """
0227 
0228     def __init__(self, value, name, description):
0229         self._value = value
0230         self._name = name
0231         self._description = description
0232 
0233     @property
0234     def value(self) -> str:
0235         return self._value
0236 
0237     @property
0238     def name(self) -> str:
0239         return self._name
0240 
0241     @property
0242     def description(self) -> str:
0243         return self._description
0244 
0245 
0246 class Enum(Type):
0247     """
0248     Represents an enum in a property.
0249     """
0250 
0251     is_enum = True
0252 
0253     def __init__(self, name: str, schema_name: str, schema: Dict):
0254         super().__init__(name[0].upper() + name[1:])
0255         self._schema_name = schema_name
0256         self._values = []
0257         for i, value_name in enumerate(schema["enum"]):
0258             self._values.append(EnumValue(i, value_name, schema["enumDescriptions"][i]))
0259 
0260     @property
0261     def values(self) -> List[EnumValue]:
0262         return self._values
0263 
0264     @property
0265     def full_name(self) -> str:
0266         return f"{self._schema_name}::{self._name}"
0267 
0268 
0269 
0270 class Ref(Type):
0271     """
0272     Ref is a reference to a type (usually an Object) that is defined elsewhere
0273     in the schema.
0274 
0275     The referenced type can be accessed through ref_type property.
0276     """
0277 
0278     _is_ref = True
0279 
0280     def __init__(self, name: str, ref_visitor: "RefVisitor"):
0281         super().__init__(name)
0282         self._ref_type = None
0283         ref_visitor.add_ref(self)
0284 
0285     @property
0286     def ref_type(self) -> Optional[Type]:
0287         return self._ref_type
0288 
0289     @property
0290     def has_primary(self) -> bool:
0291         return self._ref_type is not None and self._ref_type.has_primary
0292 
0293 
0294 class BuiltinType(Type):
0295     """
0296     Base class for builtin types
0297 
0298     Builtin types are types that are built in the C++ language (int, bool)
0299     and, for the purpose of the schema_generator, also any known Qt classes.
0300     """
0301 
0302     _is_builtin: bool = True
0303     _include_name: str
0304     _is_qt_type: bool = False
0305 
0306     def __init__(self, name, orig_ref: Ref = None):
0307         super().__init__(name)
0308         self._orig_ref = orig_ref
0309 
0310     @classmethod
0311     @property
0312     def include_name(cls) -> str:
0313         return cls._include_name
0314 
0315     @classmethod
0316     @property
0317     def is_qt_type(cls) -> bool:
0318         return cls._is_qt_type
0319 
0320     @property
0321     def has_primary(self) -> bool:
0322         return False
0323 
0324     @property
0325     def orig_ref(self):
0326         return self._orig_ref
0327 
0328 
0329 class Boolean(BuiltinType):
0330     """Represents a C++ bool type."""
0331 
0332     def __init__(self):
0333         super().__init__("bool")
0334 
0335 
0336 class Integer(BuiltinType):
0337     """Represents a C++ int type."""
0338 
0339     def __init__(self):
0340         super().__init__("int")
0341 
0342 
0343 class QList(BuiltinType):
0344     """
0345     Represents a QList class.
0346 
0347     The element_type property holds the type of the elements in the array.
0348     """
0349 
0350     _include_name = "QList"
0351     _is_qt_type = True
0352     _is_array = True
0353 
0354     def __init__(
0355         self,
0356         element_type: Dict,
0357         name_hint: str,
0358         schema_name: str,
0359         ref_visitor: "RefVisitor",
0360     ):
0361         super().__init__("QList")
0362         self._element_type = Type.parse(
0363             element_type, name_hint, schema_name, ref_visitor
0364         )
0365 
0366     @property
0367     def element_type(self) -> Type:
0368         return self._element_type
0369 
0370     @property
0371     def full_name(self) -> str:
0372         return f"QList<{self._element_type.full_name}>"
0373 
0374 
0375 class QVariantMap(BuiltinType):
0376     """
0377     Represents a QVariantMap class.
0378     """
0379 
0380     _include_name = "QVariantMap"
0381     _is_qt_type = True
0382 
0383     def __init__(self):
0384         super().__init__("QVariantMap")
0385 
0386 
0387 class QDate(BuiltinType):
0388     """
0389     Represents a QDate class.
0390     """
0391 
0392     _include_name = "QDate"
0393     _is_qt_type = True
0394 
0395     def __init__(self, orig_ref: Ref = None):
0396         super().__init__("QDate", orig_ref)
0397 
0398 
0399 class QString(BuiltinType):
0400     """
0401     Represents a QString class.
0402 
0403     This maps a plain string (in the schema) to QString.
0404     """
0405 
0406     _include_name = "QString"
0407     _is_qt_type = True
0408 
0409     def __init__(self, orig_ref: Ref = None):
0410         super().__init__("QString", orig_ref)