File indexing completed on 2024-04-21 05:41:05
0001 import logging 0002 import re 0003 import typing 0004 0005 TYPE_RX = r"(?P<prefix>\s+type:)(?P<type>[\w\*.<>|]+)" 0006 0007 BASE_NAME_DICT = { 0008 "QtObject": "QtQml.QtObject", 0009 "Item": "QtQuick.Item", 0010 } 0011 0012 0013 def post_process_type(rx, text, type): 0014 match = rx.search(text) 0015 if match: 0016 type = match.group("type") 0017 text = text[:match.start("prefix")] + text[match.end("type"):] 0018 return text, type 0019 0020 def is_cxx_comment(text): 0021 if not isinstance(text, str): 0022 text = str(text) 0023 return text.startswith("//") 0024 0025 0026 class QmlBaseComponent(): 0027 def __init__(self, name, version = None, should_separate_blocks = True): 0028 self.name = name 0029 self.base_name = "" 0030 self.elements = [] 0031 self.should_separate_blocks = should_separate_blocks 0032 0033 lst = name.split(".") 0034 self.class_name = lst[-1] 0035 self.namespaces = lst[:-1] 0036 0037 def get_attributes(self): 0038 return [x for x in self.elements if isinstance(x, QmlAttribute)] 0039 0040 def get_properties(self): 0041 return [x for x in self.elements if isinstance(x, QmlProperty)] 0042 0043 def get_functions(self): 0044 return [x for x in self.elements if isinstance(x, QmlFunction)] 0045 0046 def get_signals(self): 0047 return [x for x in self.elements if isinstance(x, QmlSignal)] 0048 0049 def add_element(self, element): 0050 self.elements.append(element) 0051 0052 def starts_with_cxx_comment(self): 0053 if not hasattr(self, "doc_is_inline") or not hasattr(self, "doc"): 0054 return False 0055 if self.doc_is_inline: 0056 return False 0057 return self.doc.startswith("//") 0058 0059 def __str__(self): 0060 lst = [] 0061 self._export_content(lst) 0062 return "\n".join(lst) 0063 0064 def _export_element(self, element, lst): 0065 doc = str(element) 0066 if doc: 0067 lst.append(doc) 0068 0069 def _export_elements(self, input_list, lst, filter=None): 0070 for element in input_list: 0071 if filter and not filter(element): 0072 continue 0073 self._export_element(element, lst) 0074 0075 def _export_element_w_access(self, element, lst, is_public, 0076 last_was_public, last_was_cxx_comment): 0077 if is_public != last_was_public: 0078 if is_public: 0079 lst.append("public:") 0080 else: 0081 lst.append("private:") 0082 elif last_was_cxx_comment and is_cxx_comment(element) and self.should_separate_blocks: 0083 lst.append("") 0084 self._export_element(element, lst) 0085 0086 def _start_class(self, lst): 0087 class_decl = "class " + self.class_name 0088 if self.base_name: 0089 for alias, replacement in self.alias.items(): 0090 self.base_name = re.sub(alias, replacement, self.base_name) 0091 self.base_name = BASE_NAME_DICT.get(self.base_name, self.base_name) 0092 0093 class_decl += " : public " + self.base_name 0094 0095 class_decl += " {" 0096 lst.append(class_decl) 0097 0098 def _end_class(self, lst): 0099 lst.append("};") 0100 0101 0102 class QmlClass(QmlBaseComponent): 0103 SINGLETON_COMMENT = "/** @remark This component is a singleton */" 0104 VERSION_COMMENT = "/** @version %s */" 0105 IMPORT_STATEMENT_COMMENT = "/** {} <br><b>Import Statement</b> \\n @code import {} @endcode */" 0106 0107 def __init__(self, name, version=None, modulename=None, should_separate_blocks = True): 0108 QmlBaseComponent.__init__(self, name, version, should_separate_blocks) 0109 self.header_comments = [] 0110 self.footer_comments = [] 0111 self.imports = [] 0112 self.alias = {} 0113 self.modulename = modulename 0114 self.version = version 0115 0116 def add_pragma(self, decl): 0117 args = decl.split(' ', 2)[1].strip() 0118 0119 if args.lower() == "singleton": 0120 self.header_comments.append(QmlClass.SINGLETON_COMMENT) 0121 0122 def add_import(self, decl): 0123 modules = decl.split() 0124 module = modules[1] 0125 if module[0] == '"': 0126 # Ignore directory or javascript imports for now 0127 return 0128 if "as" in modules: 0129 self.alias[modules[modules.index("as")+1]] = modules[1] 0130 self.imports.append(module) 0131 0132 def add_header_comment(self, obj): 0133 self.header_comments.append(obj) 0134 0135 def add_footer_comment(self, obj): 0136 self.footer_comments.append(obj) 0137 0138 def _export_header(self, lst): 0139 for module in self.imports: 0140 lst.append("using namespace %s;" % module.replace('.', '::')) 0141 if self.namespaces: 0142 lst.append("namespace %s {" % '::'.join(self.namespaces)) 0143 0144 lst.extend([str(x) for x in self.header_comments]) 0145 0146 if self.modulename: 0147 # we want to insert a newline if there has been any comments (usually a description of the class) 0148 newline = "" 0149 0150 # is there any comments before this one? 0151 any_comments = len(self.header_comments) > 0 0152 0153 # and if there is any comments, is the last one a SPDX message? they don't render, so don't insert a 0154 # useless newline 0155 is_spdx_last = any_comments and ("SPDX" not in self.header_comments[len(self.header_comments) - 1]) 0156 0157 if any_comments and is_spdx_last: 0158 newline = "\\n" 0159 0160 lst.append(QmlClass.IMPORT_STATEMENT_COMMENT.format(newline, self.modulename)) 0161 if self.version: 0162 lst.append(QmlClass.VERSION_COMMENT % self.version) 0163 0164 0165 def _export_footer(self, lst): 0166 lst.extend([str(x) for x in self.footer_comments]) 0167 0168 if self.namespaces: 0169 lst.append("}") 0170 0171 def _export_content(self, lst): 0172 self._export_header(lst) 0173 0174 # Public members. 0175 self._start_class(lst) 0176 0177 last_element_was_public = False 0178 last_element_was_cxx_comment = False 0179 for element in self.elements: 0180 if str(element) == "" or isinstance(element, str): 0181 self._export_element_w_access(element, lst, 0182 last_element_was_public, last_element_was_public, 0183 last_element_was_cxx_comment) 0184 last_element_was_cxx_comment = is_cxx_comment(element) 0185 elif element.is_public_element(): 0186 self._export_element_w_access(element, lst, True, 0187 last_element_was_public, last_element_was_cxx_comment) 0188 last_element_was_public = True 0189 last_element_was_cxx_comment = False 0190 else: 0191 self._export_element_w_access(element, lst, False, 0192 last_element_was_public, last_element_was_cxx_comment) 0193 last_element_was_public = False 0194 last_element_was_cxx_comment = False 0195 0196 self._end_class(lst) 0197 self._export_footer(lst) 0198 0199 def is_public_element(self): 0200 return True 0201 0202 0203 class QmlComponent(QmlBaseComponent): 0204 """A component inside a QmlClass""" 0205 0206 def __init__(self, name): 0207 QmlBaseComponent.__init__(self, name) 0208 self.comment = None 0209 0210 def _export_content(self, lst): 0211 component_id = self.get_component_id() 0212 if component_id: 0213 if self.comment: 0214 lst.append(self.comment) 0215 0216 lst.append("%s %s;" % (self.class_name, component_id)) 0217 0218 # Export child components with the top-level component. This avoids 0219 # very deep nesting in the generated documentation. 0220 self._export_elements(self.elements, lst, filter=lambda x: 0221 isinstance(x, QmlComponent)) 0222 0223 def get_component_id(self): 0224 # Returns the id of the component, if it has one 0225 for attr in self.get_attributes(): 0226 if attr.name == "id": 0227 return attr.value 0228 return None 0229 0230 def is_public_element(self): 0231 return False 0232 0233 0234 class QmlArgument(object): 0235 def __init__(self, name): 0236 self.type = "" 0237 self.name = name 0238 self.default_value = None 0239 self.spread = False 0240 0241 def __str__(self): 0242 if self.spread: 0243 return '.../*{}*/'.format(self.name) 0244 elif self.type == "": 0245 return self.name + self.default_value_string() 0246 else: 0247 return self.type + " " + self.name + self.default_value_string() 0248 0249 def default_value_string(self): 0250 if self.default_value is None: 0251 return '' 0252 else: 0253 return ' = {}'.format(self.default_value) 0254 0255 def is_public_element(self): 0256 return True 0257 0258 0259 class QmlAttribute(object): 0260 def __init__(self): 0261 self.name = "" 0262 self.value = "" 0263 self.type = "var" 0264 self.doc = "" 0265 0266 def __str__(self): 0267 if self.name != "id": 0268 lst = [] 0269 if len(self.doc) > 0: 0270 lst.append(self.doc) 0271 lst.append(self.type + " " + self.name + ";") 0272 return "\n".join(lst) 0273 else: 0274 return "" 0275 0276 def is_public_element(self): 0277 return False 0278 0279 0280 class QmlProperty(object): 0281 type_rx = re.compile(TYPE_RX) 0282 0283 DEFAULT_PROPERTY_COMMENT = "/** @remark This is the default property */" 0284 READONLY_PROPERTY_COMMENT = "/** @remark This property is read-only */" 0285 0286 def __init__(self): 0287 self.type = "" 0288 self.is_default = False 0289 self.is_readonly = False 0290 self.name = "" 0291 self.doc = "" 0292 self.doc_is_inline = False 0293 0294 def __str__(self): 0295 self.post_process_doc() 0296 lst = [] 0297 if not self.doc_is_inline: 0298 lst.append(self.doc + "\n") 0299 if self.is_default: 0300 lst.append(self.DEFAULT_PROPERTY_COMMENT + "\n") 0301 elif self.is_readonly: 0302 lst.append(self.READONLY_PROPERTY_COMMENT + "\n") 0303 lst.append("Q_PROPERTY(%s %s READ dummyGetter_%s_ignore)" 0304 % (self.type, self.name, self.name)) 0305 if self.doc_is_inline: 0306 lst.append(" " + self.doc) 0307 return "".join(lst) 0308 0309 def post_process_doc(self): 0310 self.doc, self.type = post_process_type(self.type_rx, self.doc, self.type) 0311 0312 def is_public_element(self): 0313 # Doxygen always adds Q_PROPERTY items as public members. 0314 return True 0315 0316 0317 class QmlFunction(object): 0318 doc_arg_rx = re.compile(r"[@\\]param" + TYPE_RX + r"\s+(?P<name>\w+)") 0319 return_rx = re.compile(r"[@\\]returns?" + TYPE_RX) 0320 0321 def __init__(self): 0322 self.type = "void" 0323 self.name = "" 0324 self.doc = "" 0325 self.doc_is_inline = False 0326 self.args = [] 0327 0328 def __str__(self): 0329 self.post_process_doc() 0330 arg_string = ", ".join([str(x) for x in self.args]) 0331 lst = [] 0332 if not self.doc_is_inline: 0333 lst.append(self.doc + "\n") 0334 lst.append("%s %s(%s);" % (self.type, self.name, arg_string)) 0335 if self.doc_is_inline: 0336 lst.append(" " + self.doc) 0337 return "".join(lst) 0338 0339 def post_process_doc(self): 0340 def repl(match): 0341 # For each argument with a specified type, update arg.type and return a typeless @param line 0342 type = match.group("type") 0343 name = match.group("name") 0344 for arg in self.args: 0345 if arg.name == name: 0346 arg.type = type 0347 break 0348 else: 0349 logging.warning("In function %s(): Unknown argument %s" % (self.name, name)) 0350 return "@param %s" % name 0351 0352 self.doc = self.doc_arg_rx.sub(repl, self.doc) 0353 self.doc, self.type = post_process_type(self.return_rx, self.doc, self.type) 0354 0355 def is_public_element(self): 0356 return True 0357 0358 0359 class QmlEnum(object): 0360 def __init__(self): 0361 self.name = "" 0362 self.doc = "" 0363 self.doc_is_inline = False 0364 self.enumerators = [] 0365 0366 def __str__(self): 0367 lst = [] 0368 0369 if self.doc and not self.doc_is_inline: 0370 lst.append(self.doc + "\n") 0371 lst.append("enum class %s {" % (self.name)) 0372 if self.doc and self.doc_is_inline: 0373 lst.append(" " + self.doc) 0374 lst.append("\n") 0375 0376 for e in self.enumerators: 0377 lst.append(str(e) + "\n") 0378 0379 lst.append("};") 0380 return "".join(lst) 0381 0382 def is_public_element(self): 0383 return True 0384 0385 0386 class QmlEnumerator(object): 0387 def __init__(self, name): 0388 self.name = name 0389 self.initializer = "" 0390 self.is_last = False 0391 self.doc = "" 0392 self.doc_is_inline = False 0393 0394 def __str__(self): 0395 lst = [] 0396 if self.doc and not self.doc_is_inline: 0397 lst.append(self.doc + "\n") 0398 lst.append("%s" % (self.name)) 0399 if self.initializer: 0400 lst.append(" = %s" % (self.initializer)) 0401 if not self.is_last: 0402 lst.append(",") 0403 if self.doc and self.doc_is_inline: 0404 lst.append(" " + self.doc) 0405 return "".join(lst) 0406 0407 def is_public_element(self): 0408 return True 0409 0410 0411 class QmlSignal(object): 0412 def __init__(self): 0413 self.name = "" 0414 self.doc = "" 0415 self.doc_is_inline = False 0416 self.args = [] 0417 0418 def __str__(self): 0419 arg_string = ", ".join([str(x) for x in self.args]) 0420 lst = [] 0421 if not self.doc_is_inline: 0422 lst.append(self.doc + "\n") 0423 lst.append("Q_SIGNALS: void %s(%s); " % (self.name, arg_string)) 0424 if self.doc_is_inline: 0425 lst.append(self.doc + "\n") 0426 # Appending "public:" here makes it possible to declare a signal without 0427 # turning all functions defined after into signals. 0428 # It could be replaced with the use of Q_SIGNAL, but my version of 0429 # Doxygen (1.8.4) does not support it 0430 lst.append("public:") 0431 return "".join(lst) 0432 0433 def is_public_element(self): 0434 # Doxygen always adds Q_SIGNALS items as public members. 0435 return True