pax_global_header00006660000000000000000000000064131333611510014507gustar00rootroot0000000000000052 comment=4f14a6da7cd9312f9818252a06a4f86de6c635d4 doxyqml-0.3.0/000077500000000000000000000000001313336115100132045ustar00rootroot00000000000000doxyqml-0.3.0/LICENSE000066400000000000000000000024241313336115100142130ustar00rootroot00000000000000Copyright 2012 Aurélien Gâteau All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. doxyqml-0.3.0/MANIFEST.in000066400000000000000000000005011313336115100147360ustar00rootroot00000000000000include bin/doxyqml bin/fromsrc.py recursive-include doxyqml *.py recursive-include tests *.py recursive-include tests/functional/*/input *.qml qmldir recursive-include tests/functional/*/expected *.qml.cpp include setup.py include NEWS include LICENSE include README.md include RELEASE_CHECK_LIST.md include MANIFEST.in doxyqml-0.3.0/NEWS000066400000000000000000000020011313336115100136740ustar00rootroot00000000000000# 2016.06.20 - 0.3.0 - Port to Python 3 (Olivier Churlaud, Aurélien Gâteau) - Skip directory imports (Aurélien Gâteau) - Support comment after class declaration (Cédric Cabessa) - Give version argument of the ctor a default value (Mathias Hasselmann) - Find qmldir for relative paths (Mathias Hasselmann) - Read import statements to help base class lookup (Mathias Hasselmann) - Generate qualified component names (Mathias Hasselmann) - Handle singleton pragmas (Mathias Hasselmann) # 2014.02.11 - 0.2.0 - Port to argparse (Aurélien Gâteau) - Add functional tests (Aurélien Gâteau) - Keep all comments, necessary to support features like @cond/@endcond (Aurélien Gâteau) - Add support for readonly properties (Burkhard Daniel) - Support for anonymous function (Niels Madan) - Improve handling of unknown arguments (Aurélien Gâteau) - Add support for \return and \param in addition to @return and @param (Aurélien Gâteau) - Make tests.py executable (Aurélien Gâteau) # 2012.11.21 - 0.1.0 - First release doxyqml-0.3.0/PKG-INFO000066400000000000000000000010631313336115100143010ustar00rootroot00000000000000Metadata-Version: 1.1 Name: doxyqml Version: 0.3.0 Summary: Doxygen input filter for QML files Home-page: http://agateau.com/projects/doxyqml Author: Aurélien Gâteau Author-email: mail@agateau.com License: BSD Description: UNKNOWN Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Documentation doxyqml-0.3.0/README.md000066400000000000000000000043131313336115100144640ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/agateau/doxyqml.svg?branch=master)](https://travis-ci.org/agateau/doxyqml) # Goals Doxyqml turns .qml into pseudo-C++ which Doxygen can then use to generate documentation. # Installing Doxyqml uses the standard Python setup tools, so you can install it with pip: pip3 install doxyqml or manually with: python3 setup.py install # Telling Doxygen to use Doxyqml To tell Doxygen about Doxyqml you must make a few changes to your Doxygen file. 1. Add the .qml extension to the `FILTER_PATTERNS` key: FILTER_PATTERNS = *.qml=doxyqml Note: On Windows you may need to use the full path of the `doxyqml.py` file instead. For example if you installed Python 3.5 in `C:\Python35`, use this: FILTER_PATTERNS = *.qml=C:\Python35\Lib\site-packages\doxyqml\doxyqml.py 2. Add the .qml extension to `FILE_PATTERNS`: FILE_PATTERNS = *.qml 3. Since Doxygen 1.8.8, you must also add the .qml extension to `EXTENSION_MAPPING`: EXTENSION_MAPPING = qml=C++ # Documenting types QML is partly-typed: functions are untyped, properties and signals are. Doxyqml provides a way to define types when they are missing or not precise enough. ## Functions Functions in QML are untyped, but you can define types in the documentation like this: /** * Create a user * @param type:string firstname User firstname * @param type:string lastname User lastname * @param type:int User age * @return type:User The User object */ function createUser(firstname, lastname, age); ## Properties QML properties are typed, so Doxyqml uses them by default. You can nevertheless overwrite the type using the same `type:` syntax. This is useful to document property aliases: /** type:string The user lastname */ property alias lastname: someObject.text ## Signals QML signals are typed, so there is no need to use the `type:` syntax to document their parameters. Using `type:` syntax in signal documentation will not work: Doxyqml won't strip it out and Doxygen will confuse it with the parameter name. /** * User just logged in * @param user The user which logged in */ signal loggedIn(User user) doxyqml-0.3.0/RELEASE_CHECK_LIST.md000066400000000000000000000012421313336115100163350ustar00rootroot00000000000000Update NEWS: r!git log --pretty=format:'- \%s (\%an)' x.y.z-1..HEAD Bump version number in doxyqml/doxyqml.py Commit Create tarball: ./setup.py sdist --formats=bztar Install tarball in virtual env: virtualenv --python python3 /tmp/doxyqml . /tmp/doxyqml/bin/activate cd /tmp/doxyqml tar xf doxyqml-$version.tar.bz2 cd doxyqml-$version ./setup.py install Run unit tests: ./tests/unit/tests.py Run functional tests: ./tests/functional/tests.py If ok, create "x.y.z" tag: git tag -a x.y.z Push: git push git push --tags Publish on PyPI: ./setup.py sdist --formats=bztar upload Update project page Blog doxyqml-0.3.0/bin/000077500000000000000000000000001313336115100137545ustar00rootroot00000000000000doxyqml-0.3.0/bin/doxyqml000077500000000000000000000003301313336115100153730ustar00rootroot00000000000000#!/usr/bin/env python3 import os import sys fromsrc_py = os.path.join(os.path.dirname(__file__), "fromsrc.py") if os.path.exists(fromsrc_py): import fromsrc from doxyqml import doxyqml sys.exit(doxyqml.main()) doxyqml-0.3.0/bin/fromsrc.py000066400000000000000000000003571313336115100160060ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Add parent dir to sys.path so that one can use Doxyqml from an uninstalled source tree. """ import os import sys parentPath = os.path.join(os.path.dirname(__file__), os.pardir) sys.path.insert(0, parentPath) doxyqml-0.3.0/doxyqml/000077500000000000000000000000001313336115100147015ustar00rootroot00000000000000doxyqml-0.3.0/doxyqml/__init__.py000066400000000000000000000000001313336115100170000ustar00rootroot00000000000000doxyqml-0.3.0/doxyqml/doxyqml.py000077500000000000000000000075021313336115100167570ustar00rootroot00000000000000#!/usr/bin/env python3 import argparse import logging import os import re import sys from . import qmlparser from .lexer import Lexer, LexerError from .qmlclass import QmlClass VERSION = "0.3.0" DESCRIPTION = "Doxygen input filter for QML files" def coord_for_idx(text, idx): head, sep, tail = text[:idx].rpartition("\n") if sep == "\n": row = head.count("\n") + 2 else: row = 1 col = len(tail) + 1 return row, col def line_for_idx(text, idx): bol = text.rfind("\n", 0, idx) if bol == -1: bol = 0 eol = text.find("\n", idx) return text[bol:eol] def info_for_error_at(text, idx): row, col = coord_for_idx(text, idx) line = line_for_idx(text, idx) msg = line + "\n" + "-" * (col - 1) + "^" return row, msg def parse_args(): parser = argparse.ArgumentParser( prog="doxyqml", description=DESCRIPTION, ) parser.add_argument("-d", "--debug", action="store_true", help="Log debug info to stderr") parser.add_argument('--version', action='version', version='%%(prog)s %s' % VERSION) parser.add_argument("qml_file", help="The QML file to parse") return parser.parse_args() def find_qmldir_file(qml_file): dir = os.path.dirname(qml_file) while True: # Check if `dir` contains a file of the name "qmldir". name = os.path.join(dir, 'qmldir') if os.path.isfile(name): return name # Pick parent of `dir`. Abort once parent stops changing, # either because we reached the root directory, or because # relative paths were used and we reached the currrent # working directory. parent = os.path.dirname(dir) if parent == dir: return None dir = parent def find_classname(qml_file): classname = os.path.basename(qml_file).split(".")[0] classversion = None modulename = '' qmldir = find_qmldir_file(qml_file) if qmldir: text = open(qmldir).read() match = re.match(r'^module\s+((?:\w|\.)+)\s*$', text, re.MULTILINE) if match: modulename = match.group(1) basedir = os.path.dirname(qmldir) rx_object_type = re.compile(r'^(\w+)\s+(\d+(?:\.\d+)*)\s+(\S+)\s*$', re.MULTILINE) for name, version, path in rx_object_type.findall(text): filename = os.path.join(basedir, path) if os.path.isfile(filename) and os.path.samefile(qml_file, filename): classversion = version classname = name break if modulename: classname = modulename + '.' + classname return classname, classversion def main(): args = parse_args() name = args.qml_file text = open(name).read() lexer = Lexer(text) try: lexer.tokenize() except LexerError as exc: logging.error("Failed to tokenize %s" % name) row, msg = info_for_error_at(text, exc.idx) logging.error("Lexer error line %d: %s\n%s", row, exc, msg) if args.debug: raise else: return -1 if args.debug: for token in lexer.tokens: print("%20s %s" % (token.type, token.value)) classname, classversion = find_classname(name) qml_class = QmlClass(classname, classversion) try: qmlparser.parse(lexer.tokens, qml_class) except qmlparser.QmlParserError as exc: logging.error("Failed to parse %s" % name) row, msg = info_for_error_at(text, exc.token.idx) logging.error("Lexer error line %d: %s\n%s", row, exc, msg) if args.debug: raise else: return -1 print(qml_class) return 0 if __name__ == "__main__": sys.exit(main()) # vi: ts=4 sw=4 et doxyqml-0.3.0/doxyqml/lexer.py000066400000000000000000000047521313336115100164020ustar00rootroot00000000000000from collections import namedtuple import re COMMENT = "comment" STRING = "string" ELEMENT = "element" BLOCK_START = "block_start" BLOCK_END = "block_end" CHAR = "char" KEYWORD = "keyword" IMPORT = "import" PRAGMA = "pragma" class LexerError(Exception): def __init__(self, msg, idx): Exception.__init__(self, msg) self.idx = idx Token = namedtuple("Token", ["type", "value", "idx"]) class Tokenizer(object): def __init__(self, token_type, rx): self.token_type = token_type self.rx = rx def __call__(self, lexer, matched_str): lexer.append_token(self.token_type, matched_str) class Lexer(object): def __init__(self, text): self.tokenizers = [ Tokenizer(COMMENT, re.compile(r"/\*.*?\*/", re.DOTALL)), Tokenizer(COMMENT, re.compile(r"//.*$", re.MULTILINE)), Tokenizer(STRING, re.compile(r'("([^\\"]|(\\.))*")')), Tokenizer(BLOCK_START, re.compile("{")), Tokenizer(BLOCK_END, re.compile("}")), Tokenizer(IMPORT, re.compile("^import\s+.*$", re.MULTILINE)), Tokenizer(PRAGMA, re.compile("^pragma\s+\w.*$", re.MULTILINE)), Tokenizer(KEYWORD, re.compile("(default\s+property|property|readonly\s+property|signal)\s+")), Tokenizer(KEYWORD, re.compile("(function)\s+[^(]")), # a named function Tokenizer(ELEMENT, re.compile(r"\w[\w.<>]*")), Tokenizer(CHAR, re.compile(".")), ] self.text = text self.idx = 0 self.tokens = [] def tokenize(self): while True: self.advance() if self.idx == len(self.text): break self.apply_tokenizers() def advance(self): while self.idx < len(self.text): if self.text[self.idx].isspace(): self.idx += 1 else: break def apply_tokenizers(self): for tokenizer in self.tokenizers: match = tokenizer.rx.match(self.text, self.idx) if not match: continue if len(match.groups()) > 0: tokenizer(self, match.group(1)) self.idx = match.end(1) return else: tokenizer(self, match.group(0)) self.idx = match.end(0) return raise LexerError("No lexer matched", self.idx) def append_token(self, type, value): self.tokens.append(Token(type, value, self.idx)) doxyqml-0.3.0/doxyqml/qmlclass.py000066400000000000000000000121731313336115100170760ustar00rootroot00000000000000import logging import re TYPE_RX = "(?P\s+type:)(?P[\w.<>|]+)" def post_process_type(rx, text, type): match = rx.search(text) if match: type = match.group("type") text = text[:match.start("prefix")] + text[match.end("type"):] return text, type class QmlClass(object): SINGLETON_COMMENT = "/** @remark This component is a singleton */" VERSION_COMMENT = "/** @since %s */" def __init__(self, name, version = None): self.name = name self.base_name = "" self.header_comments = [] self.footer_comments = [] self.elements = [] self.imports = [] if version: self.header_comments.append(QmlClass.VERSION_COMMENT % version) def get_properties(self): return [x for x in self.elements if isinstance(x, QmlProperty)] def get_functions(self): return [x for x in self.elements if isinstance(x, QmlFunction)] def get_signals(self): return [x for x in self.elements if isinstance(x, QmlSignal)] def add_element(self, element): self.elements.append(element) def add_header_comment(self, obj): self.header_comments.append(obj) def add_footer_comment(self, obj): self.footer_comments.append(obj) def add_import(self, decl): module = decl.split(' ')[1] if module[0] == '"': # Ignore directory or javascript imports for now return self.imports.append(module) def add_pragma(self, decl): args = decl.split(' ', 2)[1].strip() if args.lower() == "singleton": self.header_comments.append(QmlClass.SINGLETON_COMMENT) def __str__(self): name = self.name.split('.') lst = [] for module in self.imports: lst.append("using namespace %s;" % module.replace('.', '::')) if len(name) > 1: lst.append("namespace %s {" % '::'.join(name[:-1])) lst.extend([str(x) for x in self.header_comments]) lst.append("class %s : public %s {" % (name[-1], self.base_name)) lst.append("public:") lst.extend([str(x) for x in self.elements]) lst.append("};") lst.extend([str(x) for x in self.footer_comments]) if len(name) > 1: lst.append("}") return "\n".join(lst) class QmlArgument(object): def __init__(self, name): self.type = "" self.name = name def __str__(self): if self.type == "": return self.name else: return self.type + " " + self.name class QmlProperty(object): type_rx = re.compile(TYPE_RX) DEFAULT_PROPERTY_COMMENT = "/** @remark This is the default property */" READONLY_PROPERTY_COMMENT = "/** @remark This property is read-only */" def __init__(self): self.type = "" self.is_default = False self.is_readonly = False self.name = "" self.doc = "" def __str__(self): self.post_process_doc() lst = [self.doc] lst.append("Q_PROPERTY(%s %s)" % (self.type, self.name)) return "\n".join(lst) def post_process_doc(self): self.doc, self.type = post_process_type(self.type_rx, self.doc, self.type) if self.is_default: self.doc = self.doc + "\n" + self.DEFAULT_PROPERTY_COMMENT elif self.is_readonly: self.doc = self.doc + "\n" + self.READONLY_PROPERTY_COMMENT class QmlFunction(object): doc_arg_rx = re.compile(r"[@\\]param" + TYPE_RX + "\s+(?P\w+)") return_rx = re.compile(r"[@\\]return" + TYPE_RX) def __init__(self): self.type = "void" self.name = "" self.doc = "" self.args = [] def __str__(self): self.post_process_doc() arg_string = ", ".join([str(x) for x in self.args]) lst = [self.doc] lst.append("%s %s(%s);" % (self.type, self.name, arg_string)) return "\n".join(lst) def post_process_doc(self): def repl(match): # For each argument with a specified type, update arg.type and return a typeless @param line type = match.group("type") name = match.group("name") for arg in self.args: if arg.name == name: arg.type = type break else: logging.warning("In function %s(): Unknown argument %s" % (self.name, name)) return "@param %s" % name self.doc = self.doc_arg_rx.sub(repl, self.doc) self.doc, self.type = post_process_type(self.return_rx, self.doc, self.type) class QmlSignal(object): def __init__(self): self.name = "" self.doc = "" self.args = [] def __str__(self): arg_string = ", ".join([str(x) for x in self.args]) lst = [self.doc] # This strange syntax makes it possible to declare a signal without # turning all functions defined after into signals. # It could be replaced with the use of Q_SIGNAL, but my version of # Doxygen (1.8.4) does not support it lst.append("Q_SIGNALS: void %s(%s); public:" % (self.name, arg_string)) return "\n".join(lst) doxyqml-0.3.0/doxyqml/qmlparser.py000066400000000000000000000125641313336115100172710ustar00rootroot00000000000000from . import lexer from .qmlclass import QmlArgument, QmlProperty, QmlFunction, QmlSignal class QmlParserError(Exception): def __init__(self, msg, token): Exception.__init__(self, msg) self.token = token class QmlParserUnexpectedTokenError(QmlParserError): def __init__(self, token): QmlParserError.__init__(self, "Unexpected token: %s" % str(token), token) def parse_class_definition(reader, cls): token = reader.consume_wo_comments() if token.type != lexer.BLOCK_START: raise QmlParserError("Expected '{' after base class name", token) last_comment = None while not reader.at_end(): token = reader.consume() if token.type == lexer.COMMENT: if last_comment: cls.add_element(last_comment) last_comment = token.value elif token.type == lexer.KEYWORD: done = parse_class_content(reader, cls, token, last_comment) last_comment = None elif token.type == lexer.BLOCK_START: skip_block(reader) elif token.type == lexer.BLOCK_END: break if last_comment: cls.add_element(last_comment) def parse_class_content(reader, cls, token, doc): keyword = token.value if keyword.endswith("property"): obj = parse_property(reader, keyword) elif keyword == "function": obj = parse_function(reader) elif keyword == "signal": obj = parse_signal(reader) else: raise QmlParserError("Unknown keyword '%s'" % keyword, token) if doc is not None: obj.doc = doc cls.add_element(obj) def parse_property(reader, property_token_value): prop = QmlProperty() prop.is_default = property_token_value.startswith("default") prop.is_readonly = property_token_value.startswith("readonly") token = reader.consume_expecting(lexer.ELEMENT) prop.type = token.value token = reader.consume_expecting(lexer.ELEMENT) prop.name = token.value return prop def parse_function(reader): obj = QmlFunction() token = reader.consume_expecting(lexer.ELEMENT) obj.name = token.value reader.consume_expecting(lexer.CHAR, "(") obj.args = parse_arguments(reader) return obj def parse_signal(reader): obj = QmlSignal() token = reader.consume_expecting(lexer.ELEMENT) obj.name = token.value idx = reader.idx token = reader.consume_wo_comments() if token.type == lexer.CHAR and token.value == "(": obj.args = parse_arguments(reader, typed=True) else: reader.idx = idx return obj def parse_arguments(reader, typed=False): token = reader.consume_wo_comments() if token.type == lexer.CHAR and token.value == ")": return [] elif token.type != lexer.ELEMENT: raise QmlParserUnexpectedTokenError(token) args = [] while True: if typed: arg_type = token.value token = reader.consume_expecting(lexer.ELEMENT) arg = QmlArgument(token.value) arg.type = arg_type else: arg = QmlArgument(token.value) args.append(arg) token = reader.consume_expecting(lexer.CHAR) if token.value == ")": return args elif token.value != ",": raise QmlParserUnexpectedTokenError(token) token = reader.consume_expecting(lexer.ELEMENT) def skip_block(reader): count = 1 while True: token = reader.consume_wo_comments() if token.type == lexer.BLOCK_START: count += 1 elif token.type == lexer.BLOCK_END: count -= 1 if count == 0: return def parse_header(reader, cls): while not reader.at_end(): token = reader.consume() if token.type == lexer.COMMENT: cls.add_header_comment(token.value) elif token.type == lexer.IMPORT: cls.add_import(token.value) elif token.type == lexer.PRAGMA: cls.add_pragma(token.value) elif token.type == lexer.ELEMENT: cls.base_name = token.value return else: raise QmlParserUnexpectedTokenError(token) def parse_footer(reader, cls): while not reader.at_end(): token = reader.consume() if token.type == lexer.COMMENT: cls.add_footer_comment(token.value) else: raise QmlParserUnexpectedTokenError(token) class TokenReader(object): def __init__(self, tokens): self.tokens = tokens self.idx = 0 def consume(self): token = self.tokens[self.idx] self.idx += 1 return token def consume_wo_comments(self): while True: token = self.consume() if token.type != lexer.COMMENT: return token def consume_expecting(self, type, value=None): token = self.consume_wo_comments() if token.type != type: raise QmlParserError("Expected token of type '%s', got '%s' instead" % (type, token.type), token) if value is not None and token.value != value: raise QmlParserError("Expected token with value '%s', got '%s' instead" % (value, token.value), token) return token def at_end(self): return self.idx == len(self.tokens) def parse(tokens, cls): reader = TokenReader(tokens) parse_header(reader, cls) parse_class_definition(reader, cls) parse_footer(reader, cls) doxyqml-0.3.0/setup.py000077500000000000000000000013451313336115100147240ustar00rootroot00000000000000#!/usr/bin/env python3 # encoding: utf-8 from distutils.core import setup from doxyqml import doxyqml setup(name="doxyqml", version=doxyqml.VERSION, description=doxyqml.DESCRIPTION, author="Aurélien Gâteau", author_email="mail@agateau.com", license="BSD", platforms=["any"], url="http://agateau.com/projects/doxyqml", packages=["doxyqml"], scripts=["bin/doxyqml"], classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Plugins", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Topic :: Documentation", ] ) doxyqml-0.3.0/tests/000077500000000000000000000000001313336115100143465ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/000077500000000000000000000000001313336115100165105ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/addtogroup/000077500000000000000000000000001313336115100206605ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/addtogroup/expected/000077500000000000000000000000001313336115100224615ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/addtogroup/expected/AddToGroup.qml.cpp000066400000000000000000000001321313336115100257610ustar00rootroot00000000000000/** \addtogroup qml_group * @{ */ class AddToGroup : public Item { public: }; /** @} */ doxyqml-0.3.0/tests/functional/addtogroup/input/000077500000000000000000000000001313336115100220175ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/addtogroup/input/AddToGroup.qml000066400000000000000000000000701313336115100245370ustar00rootroot00000000000000/** \addtogroup qml_group * @{ */ Item { } /** @} */ doxyqml-0.3.0/tests/functional/basic/000077500000000000000000000000001313336115100175715ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/basic/expected/000077500000000000000000000000001313336115100213725ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/basic/expected/FunctionArgs.qml.cpp000066400000000000000000000015641313336115100252760ustar00rootroot00000000000000using namespace QtQuick; /* * Header bla */ /** * A very simple item */ class FunctionArgs : public Item { public: /** * The 'foo' property */ Q_PROPERTY(int foo) Q_SIGNALS: void clicked(int x, int y); public: Q_SIGNALS: void activated(); public: /** * Do something with arg1 and arg2 * @param arg1 first argument * @param arg2 second argument */ void doSomething(string arg1, int arg2); /** * A badly documented function. Missing one argument and documenting a * non-existing document * @param foo first argument * @param baz this argument does not exist */ void badlyDocumented(string foo, bar); Q_PROPERTY(string escaped) Q_PROPERTY(string block) /** * Compute the arg^2 * @return the result */ int square(arg); /// One-line comment void refresh(); Q_PROPERTY(int weirdProperty) /* baz */ /* foo */ }; doxyqml-0.3.0/tests/functional/basic/input/000077500000000000000000000000001313336115100207305ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/basic/input/FunctionArgs.qml000066400000000000000000000021411313336115100240430ustar00rootroot00000000000000/* * Header bla */ import QtQuick 1.1 /** * A very simple item */ Item { /** * The 'foo' property */ property int foo signal clicked(int x, int y) signal activated /** * Do something with arg1 and arg2 * @param type:string arg1 first argument * @param type:int arg2 second argument */ function doSomething(arg1, arg2) { console.log("arg1=" + arg1); } /** * A badly documented function. Missing one argument and documenting a * non-existing document * @param type:string foo first argument * @param type:int baz this argument does not exist */ function badlyDocumented(foo, bar) { } property string escaped: "a string \n \" \t with escaped chars" property string block: "a string with some block {({ ] } chars" /** * Compute the arg^2 * @return type:int the result */ function square(arg) { return arg * arg; } /// One-line comment function refresh() { } Item { } property /* foo */ int /* bar */ weirdProperty /* baz */ : /* foo */ 12 } doxyqml-0.3.0/tests/functional/endcond/000077500000000000000000000000001313336115100201225ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/endcond/expected/000077500000000000000000000000001313336115100217235ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/endcond/expected/Endcond.qml.cpp000066400000000000000000000003361313336115100245730ustar00rootroot00000000000000using namespace QtQuick; class Endcond : public Item { public: /** * The 'foo' property */ Q_PROPERTY(int foo) /// @cond TEST /// A test property, not visible by default Q_PROPERTY(int test) /// @endcond TEST }; doxyqml-0.3.0/tests/functional/endcond/input/000077500000000000000000000000001313336115100212615ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/endcond/input/Endcond.qml000066400000000000000000000003161313336115100233460ustar00rootroot00000000000000import QtQuick 1.1 Item { /** * The 'foo' property */ property int foo /// @cond TEST /// A test property, not visible by default property int test /// @endcond TEST } doxyqml-0.3.0/tests/functional/qualified-name/000077500000000000000000000000001313336115100213715ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/qualified-name/expected/000077500000000000000000000000001313336115100231725ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/qualified-name/expected/QualifiedName.qml.cpp000066400000000000000000000002031313336115100271650ustar00rootroot00000000000000namespace test::doxyqml { /** @since 2.0 */ class QualifiedName : public Item { public: Q_PROPERTY(int x) Q_PROPERTY(int y) }; } doxyqml-0.3.0/tests/functional/qualified-name/expected/qml/000077500000000000000000000000001313336115100237635ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/qualified-name/expected/qml/AlsoQualified.qml.cpp000066400000000000000000000002031313336115100277740ustar00rootroot00000000000000namespace test::doxyqml { /** @since 2.0 */ class AlsoQualified : public Item { public: Q_PROPERTY(int x) Q_PROPERTY(int y) }; } doxyqml-0.3.0/tests/functional/qualified-name/input/000077500000000000000000000000001313336115100225305ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/qualified-name/input/QualifiedName.qml000066400000000000000000000000571313336115100257510ustar00rootroot00000000000000Item { property int x property int y } doxyqml-0.3.0/tests/functional/qualified-name/input/qml/000077500000000000000000000000001313336115100233215ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/qualified-name/input/qml/AlsoQualified.qml000066400000000000000000000000571313336115100265600ustar00rootroot00000000000000Item { property int x property int y } doxyqml-0.3.0/tests/functional/qualified-name/input/qmldir000066400000000000000000000001401313336115100237360ustar00rootroot00000000000000module test.doxyqml QualifiedName 2.0 QualifiedName.qml AlsoQualified 2.0 qml/AlsoQualified.qml doxyqml-0.3.0/tests/functional/relative-import/000077500000000000000000000000001313336115100216335ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/relative-import/expected/000077500000000000000000000000001313336115100234345ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/relative-import/expected/RelativeImport.qml.cpp000066400000000000000000000002651313336115100277010ustar00rootroot00000000000000using namespace QtQuick; using namespace QtQuick::Controls; using namespace QtQuick::Controls::Styles; class RelativeImport : public QtObject { public: Q_PROPERTY(string hello) }; doxyqml-0.3.0/tests/functional/relative-import/input/000077500000000000000000000000001313336115100227725ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/relative-import/input/RelativeImport.qml000066400000000000000000000002501313336115100264500ustar00rootroot00000000000000import QtQuick 2.0 import QtQuick.Controls 1.0 import QtQuick.Controls.Styles 1.0 import "." import "qrc:///something" QtObject { property string hello: "world" } doxyqml-0.3.0/tests/functional/signals/000077500000000000000000000000001313336115100201505ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/signals/expected/000077500000000000000000000000001313336115100217515ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/signals/expected/Signal.qml.cpp000066400000000000000000000001751313336115100244650ustar00rootroot00000000000000class Signal : public Item { public: void move(x, y); Q_SIGNALS: void moved(int x, int y); public: void doSomething(); }; doxyqml-0.3.0/tests/functional/signals/input/000077500000000000000000000000001313336115100213075ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/signals/input/Signal.qml000066400000000000000000000001401313336115100232320ustar00rootroot00000000000000Item { function move(x, y); signal moved(int x, int y); function doSomething(); } doxyqml-0.3.0/tests/functional/singleton/000077500000000000000000000000001313336115100205125ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/singleton/expected/000077500000000000000000000000001313336115100223135ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/singleton/expected/Style.qml.cpp000066400000000000000000000001611313336115100247050ustar00rootroot00000000000000using namespace QtQuick; /** @remark This component is a singleton */ class Style : public QtObject { public: }; doxyqml-0.3.0/tests/functional/singleton/input/000077500000000000000000000000001313336115100216515ustar00rootroot00000000000000doxyqml-0.3.0/tests/functional/singleton/input/Style.qml000066400000000000000000000000621313336115100234620ustar00rootroot00000000000000import QtQuick 2.0 pragma Singleton QtObject { } doxyqml-0.3.0/tests/functional/tests.py000077500000000000000000000104361313336115100202330ustar00rootroot00000000000000#!/usr/bin/env python3 # encoding: utf-8 import argparse import difflib import os import shutil import sys import subprocess def list_files(topdir): result = [] for root, dirs, files in os.walk(topdir): for name in files: subdir = root[len(topdir) + 1:] result.append(subdir and os.path.join(subdir, name) or name) return result class Test(object): def __init__(self, name, executable): self.name = name self.executable = executable self.input_dir = os.path.join(self.name, "input") self.output_dir = os.path.join(self.name, "output") self.expected_dir = os.path.join(self.name, "expected") def build(self): ok = True if os.path.exists(self.output_dir): shutil.rmtree(self.output_dir) os.mkdir(self.output_dir) for name in list_files(self.input_dir): if not name.endswith(".qml"): continue out_path = os.path.join(self.output_dir, name + ".cpp") out_dir = os.path.dirname(out_path) if not os.path.isdir(out_dir): os.makedirs(out_dir) with open(out_path, "w") as out: ret = subprocess.call( [self.executable, name], stdout=out, cwd=self.input_dir) if ret != 0: self.error("doxyqml failed on {}".format(name)) ok = False return ok def update(self): if os.path.exists(self.expected_dir): shutil.rmtree(self.expected_dir) shutil.copytree(self.output_dir, self.expected_dir) def compare(self): lst = list_files(self.expected_dir) if not lst: self.error("expected_dir '{}' is empty".format(self.expected_dir)) return False ok = True for name in lst: if name.startswith("."): continue out_path = os.path.join(self.output_dir, name) if not os.path.exists(out_path): self.error("File {} does not exist".format(out_path)) ok = False continue out_lines = open(out_path).readlines() expected_path = os.path.join(self.expected_dir, name) expected_lines = open(expected_path).readlines() delta = difflib.unified_diff(expected_lines, out_lines, fromfile="expected", tofile="output") delta_lines = list(delta) if delta_lines: ok = False self.error("Failure on {}".format(name)) for line in delta_lines: sys.stderr.write(line) return ok def error(self, msg): print("{}: ERROR: {}".format(self.name, msg)) def main(): script_dir = os.path.dirname(__file__) or "." default_doxyqml = os.path.abspath(os.path.join(script_dir, os.pardir, os.pardir, "bin", "doxyqml")) parser = argparse.ArgumentParser() parser.add_argument("-u", "--update", help="Update expected output from test ID", metavar="ID") parser.add_argument("--doxyqml", default=default_doxyqml, help="Path to the doxyqml executable ({})".format(default_doxyqml)) parser.add_argument("test_id", nargs="?", help="Run specified test only") args = parser.parse_args() executable = os.path.abspath(args.doxyqml) os.chdir(script_dir) if args.update: print("Updating {}...".format(args.update)) test = Test(args.update, executable) if not test.build(): return 1 test.update() return 0 if args.test_id: if not os.path.isdir(args.test_id): parser.error("Invalid test id '{}'".format(args.test_id)) test_list = [args.test_id] else: test_list = [x for x in os.listdir(".") if os.path.isdir(x)] errors = 0 for test_dir in test_list: print("Testing {}...".format(test_dir)) test = Test(test_dir, executable) if not (test.build() and test.compare()): errors += 1 continue print("") if errors: print("Failure: {} errors".format(errors)) return 1 else: print("Success") return 0 if __name__ == "__main__": sys.exit(main()) # vi: ts=4 sw=4 et doxyqml-0.3.0/tests/unit/000077500000000000000000000000001313336115100153255ustar00rootroot00000000000000doxyqml-0.3.0/tests/unit/qmlclasstestcase.py000066400000000000000000000052241313336115100212550ustar00rootroot00000000000000import re from unittest import TestCase from doxyqml.qmlclass import QmlFunction, QmlArgument, QmlProperty class QmlFunctionTestCase(TestCase): def test_post_process_doc_at(self): self._test_post_process_doc(""" /** * Create a user * * @param type:string firstname The user firstname * @param type:string lastname The user lastname * @param type:int age The user age * @param misc A parameter with no type * @return type:User A new user */ """) def test_post_process_doc_backslash(self): self._test_post_process_doc(r""" /** * Create a user * * \param type:string firstname The user firstname * \param type:string lastname The user lastname * \param type:int age The user age * \param misc A parameter with no type * \return type:User A new user */ """) def _test_post_process_doc(self, doc): fcn = QmlFunction() fcn.args = [ QmlArgument("firstname"), QmlArgument("lastname"), QmlArgument("age"), QmlArgument("misc"), ] fcn.doc = doc fcn.post_process_doc() self.assertEqual(fcn.args[0].type, "string") self.assertEqual(fcn.args[1].type, "string") self.assertEqual(fcn.args[2].type, "int") self.assertEqual(fcn.args[3].type, "") self.assertEqual(fcn.type, "User") # "[@\]param type:..." are turned into "@param" expected_doc = re.sub(r"[@\\]param type:\w+", r"@param", doc) # "[@\]return type:..." are turned into "[@\]return" expected_doc = re.sub(r"([@\\])return type:\w+", r"\1return", expected_doc) self.assertMultiLineEqual(fcn.doc, expected_doc) class QmlPropertyTestCase(TestCase): def test_property_type(self): prop = QmlProperty() prop.doc = "/// type:User The current user" prop.type = "alias" prop.post_process_doc() self.assertEqual(prop.type, "User") self.assertEqual(prop.doc, "/// The current user") def test_no_property_type(self): prop = QmlProperty() prop.doc = "/// The user age" prop.type = "int" prop.post_process_doc() self.assertEqual(prop.type, "int") self.assertEqual(prop.doc, "/// The user age") def test_default_property(self): prop = QmlProperty() prop.doc = "/// Children" prop.type = "list" prop.is_default = True prop.post_process_doc() self.assertEqual(prop.doc, "/// Children\n" + QmlProperty.DEFAULT_PROPERTY_COMMENT) doxyqml-0.3.0/tests/unit/qmlparsertestcase.py000066400000000000000000000100631313336115100214410ustar00rootroot00000000000000from unittest import TestCase from doxyqml.lexer import Lexer from doxyqml.qmlclass import QmlClass from doxyqml import qmlparser class QmlParserTestCase(TestCase): def test(self): src = "Item { function foo() {} function bar() {} }" lexer = Lexer(src) lexer.tokenize() qmlclass = QmlClass("Foo") qmlparser.parse(lexer.tokens, qmlclass) self.assertEqual(qmlclass.base_name, "Item") functions = qmlclass.get_functions() self.assertEqual(functions[0].name, "foo") self.assertEqual(functions[1].name, "bar") self.assertEqual(len(functions), 2) def test_default_property(self): src = """Item { /// v1 doc default property int v1 /// v2 doc property int v2 }""" lexer = Lexer(src) lexer.tokenize() qmlclass = QmlClass("Foo") qmlparser.parse(lexer.tokens, qmlclass) properties = qmlclass.get_properties() self.assertEqual(properties[0].name, "v1") self.assertEqual(properties[0].type, "int") self.assertEqual(properties[0].doc, "/// v1 doc") self.assertTrue(properties[0].is_default) self.assertEqual(properties[1].name, "v2") self.assertEqual(properties[1].type, "int") self.assertEqual(properties[1].doc, "/// v2 doc") self.assertFalse(properties[1].is_default) def test_readonly_property(self): src = """Item { /// v1 doc readonly property int v1 /// v2 doc property int v2 }""" lexer = Lexer(src) lexer.tokenize() qmlclass = QmlClass("Foo") qmlparser.parse(lexer.tokens, qmlclass) properties = qmlclass.get_properties() self.assertEqual(properties[0].name, "v1") self.assertEqual(properties[0].type, "int") self.assertEqual(properties[0].doc, "/// v1 doc") self.assertTrue(properties[0].is_readonly) self.assertEqual(properties[1].name, "v2") self.assertEqual(properties[1].type, "int") self.assertEqual(properties[1].doc, "/// v2 doc") self.assertFalse(properties[1].is_readonly) def test_var_property(self): src = """Item { property var varProperty: { "key1": "value1", "key2": "value2" } }""" lexer = Lexer(src) lexer.tokenize() qmlclass = QmlClass("Foo") qmlparser.parse(lexer.tokens, qmlclass) properties = qmlclass.get_properties() self.assertEqual(properties[0].name, "varProperty") self.assertEqual(properties[0].type, "var") def test_function_property(self): src = """Item { property var fnProperty: function (arg1, arg2) { return arg1 + arg2; } }""" lexer = Lexer(src) lexer.tokenize() qmlclass = QmlClass("Foo") qmlparser.parse(lexer.tokens, qmlclass) properties = qmlclass.get_properties() self.assertEqual(properties[0].name, "fnProperty") self.assertEqual(properties[0].type, "var") def test_normal_arguments(self): src = """Item { function foo(arg1, arg2) { return arg1 + arg2; } }""" lexer = Lexer(src) lexer.tokenize() qmlclass = QmlClass("Foo") qmlparser.parse(lexer.tokens, qmlclass) functions = qmlclass.get_functions() self.assertEqual(functions[0].name, "foo") self.assertEqual(functions[0].type, "void") def test_keyword_arguments(self): src = """Item { function foo(propertyArgument, signalArgument) { return propertyArgument + signalArgument; } }""" lexer = Lexer(src) lexer.tokenize() qmlclass = QmlClass("Foo") qmlparser.parse(lexer.tokens, qmlclass) functions = qmlclass.get_functions() self.assertEqual(functions[0].name, "foo") self.assertEqual(functions[0].type, "void") doxyqml-0.3.0/tests/unit/tests.py000077500000000000000000000005071313336115100170460ustar00rootroot00000000000000#!/usr/bin/env python3 import os import unittest import sys doxyqml_path = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) sys.path.insert(0, doxyqml_path) from qmlclasstestcase import * from qmlparsertestcase import * def main(): unittest.main() if __name__ == "__main__": main() # vi: ts=4 sw=4 et