diff options
-rw-r--r-- | MANIFEST.in | 17 | ||||
-rw-r--r-- | qface/__about__.py | 2 | ||||
-rw-r--r-- | qface/filters.py | 28 | ||||
-rw-r--r-- | qface/generator.py | 55 | ||||
-rw-r--r-- | qface/helper/doc.py | 4 | ||||
-rw-r--r-- | qface/helper/qtcpp.py | 2 | ||||
-rw-r--r-- | qface/idl/domain.py | 2 | ||||
-rw-r--r-- | qface/idl/listener.py | 1 | ||||
-rw-r--r-- | qface/templates/__init__.py | 0 | ||||
-rw-r--r-- | qface/templates/qface/__init__.py | 0 | ||||
-rw-r--r-- | setup.py | 3 | ||||
-rw-r--r-- | tests/in/com.pelagicore.ivi.tuner.qface | 2 | ||||
-rw-r--r-- | tests/in/org.example.echo.qface | 2 | ||||
-rw-r--r-- | tests/in/org.example.failing.qface | 5 | ||||
-rw-r--r-- | tests/test_comments.py | 6 | ||||
-rw-r--r-- | tests/test_parser.py | 22 | ||||
-rw-r--r-- | tests/test_qtcpp_helper.py | 14 |
17 files changed, 135 insertions, 30 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 10543f7..064b9f8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,12 @@ -include LICENSE.GPLV3 -include qface/builtin/qtcpp/templates/* -include qface/builtin/qtcpp/log.yaml -include qface/builtin/qtqml/templates/* -include qface/builtin/qtqml/log.yaml +include LICENSE +include README.md + +graft qface/templates + +global-exclude *.so +global-exclude *.pyc +global-exclude *~ +global-exclude \#* +global-exclude .git* +global-exclude .DS_Store + diff --git a/qface/__about__.py b/qface/__about__.py index a9d62f6..0d6a726 100644 --- a/qface/__about__.py +++ b/qface/__about__.py @@ -9,7 +9,7 @@ except NameError: __title__ = "qface" __summary__ = "A generator framework based on a common modern IDL" __url__ = "https://pelagicore.github.io/qface/" -__version__ = "1.5" +__version__ = "1.6" __author__ = "JRyannel" __author_email__ = "qface-generator@googlegroups.com" __copyright__ = "2017 Pelagicore" diff --git a/qface/filters.py b/qface/filters.py index af9fc1f..c5ecab8 100644 --- a/qface/filters.py +++ b/qface/filters.py @@ -2,25 +2,29 @@ import json import hashlib -def jsonify(obj): +def jsonify(symbol): + """ returns json format for symbol """ try: # all symbols have a toJson method, try it - return json.dumps(obj.toJson(), indent=' ') + return json.dumps(symbol.toJson(), indent=' ') except AttributeError: pass - return json.dumps(obj, indent=' ') + return json.dumps(symbol, indent=' ') -def upper_first(s): - s = str(s) - return s[0].upper() + s[1:] +def upper_first(symbol): + """ uppercase first letter """ + name = str(symbol) + return name[0].upper() + name[1:] -def hash(s, hash_type='sha1'): - h = hashlib.new(hash_type) - h.update(str(s).encode('utf-8')) - return h.hexdigest() +def hash(symbol, hash_type='sha1'): + """ create a hash code from symbol """ + code = hashlib.new(hash_type) + code.update(str(symbol).encode('utf-8')) + return code.hexdigest() -def path(s): - return str(s).replace('.', '/') +def path(symbol): + """ replaces '.' with '/' """ + return str(symbol).replace('.', '/') diff --git a/qface/generator.py b/qface/generator.py index 997ace8..c2e018b 100644 --- a/qface/generator.py +++ b/qface/generator.py @@ -1,3 +1,4 @@ + # Copyright (c) Pelagicore AB 2016 from jinja2 import Environment, Template @@ -5,12 +6,13 @@ from jinja2 import FileSystemLoader, PackageLoader, ChoiceLoader from jinja2 import TemplateSyntaxError, TemplateNotFound, TemplateError from path import Path from antlr4 import FileStream, CommonTokenStream, ParseTreeWalker -from antlr4.error import DiagnosticErrorListener +from antlr4.error import DiagnosticErrorListener, ErrorListener import shelve import logging import hashlib import yaml import click +import sys from .idl.parser.TLexer import TLexer from .idl.parser.TParser import TParser @@ -43,8 +45,30 @@ def lower_first_filter(s): return s[0].lower() + s[1:] +class ReportingErrorListener(ErrorListener.ErrorListener): + def __init__(self, document): + self.document = document + + def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): + msg = '{0}:{1}:{2} {2}'.format(self.document, line, column, msg) + click.secho(msg, fg='red') + raise ValueError(msg) + + def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs): + click.secho('ambiguity', fg='red') + + def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs): + click.secho('reportAttemptingFullContext', fg='red') + + def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs): + click.secho('reportContextSensitivity', fg='red') + + class Generator(object): """Manages the templates and applies your context data""" + strict = False + """ enables strict code generation """ + def __init__(self, search_path: str): loader = ChoiceLoader([ FileSystemLoader(search_path), @@ -86,18 +110,24 @@ class Generator(object): """Using a template file name it renders a template into a file given a context """ + error = False try: self._write(file_path, template, context, preserve) except TemplateSyntaxError as exc: # import pdb; pdb.set_trace() message = '{0}:{1} error: {2}'.format(exc.filename, exc.lineno, exc.message) click.secho(message, fg='red') + error = True except TemplateNotFound as exc: message = '{0} error: Template not found'.format(exc.name) click.secho(message, fg='red') + error = True except TemplateError as exc: message = 'error: {0}'.format(exc.message) click.secho(message, fg='red') + error = True + if error and Generator.strict: + sys.exit(-1) def _write(self, file_path: Path, template: str, context: dict, preserve: bool = False): path = self.destination / Path(self.apply(file_path, context)) @@ -125,9 +155,26 @@ class Generator(object): class FileSystem(object): """QFace helper functions to work with the file system""" + strict = False + """ enables strict parsing """ @staticmethod def parse_document(document: Path, system: System = None): + error = False + try: + return FileSystem._parse_document(document, system) + except FileNotFoundError as e: + click.secho('{0}: file not found'.format(document), fg='red') + error = True + except ValueError as e: + click.secho('Error parsing document {0}'.format(document)) + error = True + if error and FileSystem.strict: + sys.exit(-1) + + + @staticmethod + def _parse_document(document: Path, system: System = None): """Parses a document and returns the resulting domain system :param path: document path to parse @@ -135,19 +182,19 @@ class FileSystem(object): """ logger.debug('parse document: {0}'.format(document)) stream = FileStream(str(document), encoding='utf-8') - system = FileSystem._parse_stream(stream, system) + system = FileSystem._parse_stream(stream, system, document) FileSystem.merge_annotations(system, document.stripext() + '.yaml') return system @staticmethod - def _parse_stream(stream, system: System = None): + def _parse_stream(stream, system: System = None, document=None): logger.debug('parse stream') system = system or System() lexer = TLexer(stream) stream = CommonTokenStream(lexer) parser = TParser(stream) - parser.addErrorListener(DiagnosticErrorListener.DiagnosticErrorListener()) + parser.addErrorListener(ReportingErrorListener(document)) tree = parser.documentSymbol() walker = ParseTreeWalker() walker.walk(DomainListener(system), tree) diff --git a/qface/helper/doc.py b/qface/helper/doc.py index 0935e67..9695136 100644 --- a/qface/helper/doc.py +++ b/qface/helper/doc.py @@ -21,7 +21,7 @@ class DocObject: The documentation object passed into the template engine """ def __init__(self): - self.brief = str() + self.brief = [] self.description = [] self.see = [] self.deprecated = False @@ -78,4 +78,6 @@ def parse_doc(s): doc.add_tag(tag, value) elif tag: # append to previous matched tag doc.add_tag(tag, line) + else: # append any loose lines to description + doc.add_tag('description', line) return doc diff --git a/qface/helper/qtcpp.py b/qface/helper/qtcpp.py index 4a7178d..db959b4 100644 --- a/qface/helper/qtcpp.py +++ b/qface/helper/qtcpp.py @@ -39,6 +39,8 @@ class Filters(object): module_name = upper_first(t.reference.module.module_name) value = next(iter(t.reference.members)) return '{0}{1}Module::{2}'.format(prefix, module_name, value) + elif t.is_flag: + return '0' elif symbol.type.is_list: nested = Filters.returnType(symbol.type.nested) return 'QVariantList()'.format(nested) diff --git a/qface/idl/domain.py b/qface/idl/domain.py index b8c611f..a2e3972 100644 --- a/qface/idl/domain.py +++ b/qface/idl/domain.py @@ -125,6 +125,7 @@ class Symbol(NamedElement): self._contentMap = ChainMap() self.type = TypeSymbol('', self) + self.kind = self.__class__.__name__.lower() """ the associated type information """ @property @@ -431,6 +432,7 @@ class Operation(Symbol): def toJson(self): o = super().toJson() o['parameters'] = [s.toJson() for s in self.parameters] + o['type'] = self.type.toJson() return o diff --git a/qface/idl/listener.py b/qface/idl/listener.py index 3704444..3a36837 100644 --- a/qface/idl/listener.py +++ b/qface/idl/listener.py @@ -214,6 +214,7 @@ class DomainListener(TListener): if ctx.intSymbol(): value = int(ctx.intSymbol().value.text, 0) self.field.value = value + self.parse_annotations(ctx, self.field) contextMap[ctx] = self.field if self.enum.is_flag: self.enumCounter <<= 1 diff --git a/qface/templates/__init__.py b/qface/templates/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/qface/templates/__init__.py +++ /dev/null diff --git a/qface/templates/qface/__init__.py b/qface/templates/qface/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/qface/templates/qface/__init__.py +++ /dev/null @@ -43,9 +43,6 @@ setup( keywords='qt code generator framework', packages=find_packages(), include_package_data=True, - package_data={ - '': ['*[!*.pyc]'] - }, install_requires=[ 'jinja2', 'path.py', diff --git a/tests/in/com.pelagicore.ivi.tuner.qface b/tests/in/com.pelagicore.ivi.tuner.qface index e8f53ae..725d4d5 100644 --- a/tests/in/com.pelagicore.ivi.tuner.qface +++ b/tests/in/com.pelagicore.ivi.tuner.qface @@ -2,7 +2,7 @@ module com.pelagicore.ivi.tuner 1.0; interface BaseTuner { - property int baseValue; + int baseValue; } diff --git a/tests/in/org.example.echo.qface b/tests/in/org.example.echo.qface index 04a93ce..087f14f 100644 --- a/tests/in/org.example.echo.qface +++ b/tests/in/org.example.echo.qface @@ -10,6 +10,8 @@ module org.example.echo 1.0 * @see org.example * @see http://qt.io * @anything hello + * + * continued description */ interface Echo { /** diff --git a/tests/in/org.example.failing.qface b/tests/in/org.example.failing.qface new file mode 100644 index 0000000..0fe3888 --- /dev/null +++ b/tests/in/org.example.failing.qface @@ -0,0 +1,5 @@ +module org.example 1.0 + +interfase xx Failed { + +}
\ No newline at end of file diff --git a/tests/test_comments.py b/tests/test_comments.py index 008dd0d..d2f18b6 100644 --- a/tests/test_comments.py +++ b/tests/test_comments.py @@ -35,8 +35,8 @@ def test_comment(): interface = system.lookup('org.example.echo.Echo') assert interface o = doc.parse_doc(interface.comment) - assert o.brief == 'the brief' - assert o.description == ['the description', 'continues {@link http://qt.io}'] + assert o.brief == ['the brief'] + assert o.description == ['the description', 'continues {@link http://qt.io}', 'continued description'] assert o.deprecated is True assert o.see == ['org.example.echo.Echo', 'org.example', 'http://qt.io'] @@ -50,4 +50,4 @@ def test_qdoc_translate(): assert interface doc.translate = qdoc_translate o = doc.parse_doc(interface.comment) - assert o.description == ['the description', 'continues \\link{http://qt.io}'] + assert o.description == ['the description', 'continues \\link{http://qt.io}', 'continued description'] diff --git a/tests/test_parser.py b/tests/test_parser.py index b2844fc..6adfe04 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,5 +1,6 @@ import logging import logging.config +import pytest from path import Path from qface.generator import FileSystem @@ -190,3 +191,24 @@ def test_interface_property(): assert prop.type.is_interface assert prop.type.reference is extension + +def test_symbol_kind(): + system = load_tuner() + tuner = system.lookup('com.pelagicore.ivi.tuner.Tuner') + assert tuner.kind == 'interface' + property = system.lookup('com.pelagicore.ivi.tuner.Tuner#primitiveModel') + assert property.kind == 'property' + + +def test_parser_exceptions(): + path = inputPath / 'org.example.failing.qface' + system = FileSystem.parse_document(path) + + try: + system = FileSystem.parse_document('not-exists') + except SystemExit as e: + pass + else: + pytest.fail('should not ome here') + + diff --git a/tests/test_qtcpp_helper.py b/tests/test_qtcpp_helper.py index a59e0d9..acc034f 100644 --- a/tests/test_qtcpp_helper.py +++ b/tests/test_qtcpp_helper.py @@ -23,6 +23,7 @@ interface Test { void echo(string message); Message message; Status status; + ApplicationState state; list<int> list001; list<Message> list002; model<int> model001; @@ -38,6 +39,14 @@ enum Status { ON, OFF } + +flag ApplicationState { + Suspended, + Hidden, + Inactive, + Active, +} + """ @@ -127,6 +136,11 @@ def test_default_value(): answer = qtcpp.Filters.defaultValue(prop) assert answer == 'ExampleModule::ON' + # check for flag + prop = interface._propertyMap['state'] + answer = qtcpp.Filters.defaultValue(prop) + assert answer == '0' + # check for list of primitive prop = interface._propertyMap['list001'] answer = qtcpp.Filters.defaultValue(prop) |