aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuergen Bocklage-Ryannel <jbocklage-ryannel@luxoft.com>2017-08-10 10:46:07 +0200
committerJuergen Bocklage-Ryannel <jbocklage-ryannel@luxoft.com>2017-08-10 10:46:07 +0200
commit1b4441d5ec3d79cfd3a03a78066072f1ca991715 (patch)
tree4c8bbddc05057f959b0ba16d6dbe0429bb5ce567
parent4368a2109bd847b900b6cf44cde924f4472b2ac5 (diff)
parent3355cd0fd006a0b29f3be512382caaee285bb7fe (diff)
Merge branch 'release/1.8.1'1.8.1
-rw-r--r--docs/extending.rst36
-rw-r--r--docs/grammar.rst100
-rw-r--r--qface/__about__.py2
-rw-r--r--qface/generator.py39
-rw-r--r--qface/helper/qtcpp.py40
-rw-r--r--qface/idl/domain.py10
-rw-r--r--qface/templates/qface/qtcpp.j24
-rw-r--r--qface/watch.py1
8 files changed, 193 insertions, 39 deletions
diff --git a/docs/extending.rst b/docs/extending.rst
index dc42cb8..042f094 100644
--- a/docs/extending.rst
+++ b/docs/extending.rst
@@ -1,6 +1,6 @@
-***************
-Extending QFace
-***************
+*********
+Extending
+*********
QFace is easy to use and easy to extend. Your generator is just a small python
script using the qface library.
@@ -75,7 +75,7 @@ The `RuleGenerator` allows you to extract the documentation rules into an extern
generator = RuleGenerator(search_path=here/'templates', destination=output)
generator.process_rules(here/'docs.yaml', system)
-The rules document is divided into several targets. Each target can have an own destination. A target is typical for example and app, client, server. Each target can have rules for the different symbols (system, module, interface, struct, enum). An each rule finally consists of a destination modifier, additional context and a documents collection.
+The rules document is divided into several targets. Each target can have an own destination. A target is typical for example and `app`, `client` or `server`. Each target can have rules for the different symbols (system, module, interface, struct, enum). An each rule finally consists of a destination modifier, additional context and a documents collection.
.. code-block:: python
@@ -126,14 +126,14 @@ The rule generator adds the ``dst``, ``project`` as also the corresponding symbo
.. rubric:: Features
-The rules document allows to conditional write files based on a feature set. The feature set must be a set of tags indicating the features which will then be checked in the ``when`` section of a rule.
+The rules document allows to conditional write files based on a feature set. The feature set must be a set of tags indicating the features which will then be checked in the ``when`` section of a rule. The ``when`` tag needs to be a list of feature switched.
The features are passed to the generator in your custom generator code. The existence of a feature tells the rules engine to check if a ``when`` section exists conditionally execute this rule.
.. code-block:: yaml
plugin:
- when: plugin_enabled
+ when: [plugin_enabled]
destination: '{{dst}}/plugin'
module:
...
@@ -157,3 +157,27 @@ Documents can be marked as preserved to prevent them to be overwritten when the
- '{{interface|lower}}.cpp'
In the example above the two interface documents will not be overwritten during a second generator call and can be edited by the user.
+
+.. rubric:: Destination and Source
+
+The ``destination`` tag allows you to specify a prefix for the target destination of the document. It should always contain the ``{{dst}}`` variable to be placed inside the project folder.
+
+The ``source`` tag specifies a prefix for the templates resolving. If the template name starts with a ``/`` the prefix will be ignored.
+
+Destination and source tags are allowed on the target level as also on each system, module and other symbol levels. A tag on a parent symbol will be the default for the child symbols.
+
+.. rubric:: Implicit symbol hierarchy
+
+This is the implicit logical hierarchy taken into account:
+
+.. code-block:: xml
+
+ <target>
+ <system>
+ <module>
+ <interface>
+ <struct>
+ <enum>
+
+Typical you place the destination prefix on the module level if your destination depends on the module symbol. For generic templates you would place the destination on the system level. On the system level you can not use child symbols (such as the module) as at this time these symbols are not known yet.
+
diff --git a/docs/grammar.rst b/docs/grammar.rst
index ac6bf02..a8048ea 100644
--- a/docs/grammar.rst
+++ b/docs/grammar.rst
@@ -2,7 +2,7 @@
Grammar
=======
-QFace (Qt interface language) is an Interface Description Languge (IDL). While it is primarily designed to define an interface between Qt, QML and C++, it is intended to be flexible enough also to be used in other contexts.
+QFace (Qt interface language) is an Interface Description Language (IDL). While it is primarily designed to define an interface between Qt, QML and C++, it is intended to be flexible enough also to be used in other contexts.
The grammar of QFace is well defined and is based on the concepts of modules as larger collection of information.
@@ -60,20 +60,104 @@ An interface is a collection of properties, operation and signals. Properties ca
signal error(string message);
}
+QFace allows to extends interfaces using the ``extends`` keyword after the interface name.
+
+.. code-block:: js
+
+ interface Station {
+ void reset();
+ signal error(string message);
+ }
+
+ interface WeatherStation extends Station {
+ real temperature;
+ }
+
+.. note::
+
+ For the sake of simplicity as an API designer you should carefully evaluate if this is required. The typical way in QFace to allow extension is normally to write your own code-generator and use type annotations.
+
+
+ .. code-block:: js
+
+ @station
+ interface WeatherStation {
+ real temperature;
+ }
+
+ The API reader does not need to know the internals of the API. The station behavior would be automatically attached by the custom generator.
+
+
+
Struct
======
+The struct resembles a data container. It consist of a set of fields where each field has a data type and a name.
+
+.. code-block:: js
+
+ struct Error {
+ string message;
+ int code;
+ };
+
+Structs can also be nested. A struct can be used everywhere where a type can be used.
+
+.. code-block:: js
+
+ interface WeatherStation {
+ real temperature;
+ Error lastError;
+ void reset();
+ signal error(Error error);
+ }
+
+
+
Enum/Flag
=========
-Types
------
+An enum and flag is an enumeration type. The value of each member is automatically assigned if missing.
+
+.. code-block:: js
+
+ enum State {
+ Null,
+ Loading,
+ Ready,
+ Error
+ }
+
+The value assignment for the enum type is sequential beginning from 0. To specify the exact value you can assign a value to the member.
-Types are either local and can be references simply by its name, or they are from external module in this case they need to be referenced with the fully qualified name (``module + '.' + name``). A type can be an interface, struct, enum or flag.
+.. code-block:: js
+
+ enum State {
+ Null = 0,
+ Loading = 1,
+ Ready = 2,
+ Error = 3
+ }
+
+The flag type defines an enumeration type where these different values are treated as a bit mask. The values are in the sequence of the 2^n.
+
+.. code-block:: js
+
+ flag Cell {
+ Null,
+ Box,
+ Wall,
+ Figure
+ }
+
+
+
+Types
+=====
-A module consist of either one or more interfaces, structs and enums/flags. They can come in any number or combination. The interface is the only type which can contain operations and signals. The struct is merely a container to transport structured data. An enum/flag allows the user to encode information used inside the struct or interface as datatype.
+Types are either local and can be references simply by its name, or they are from external module in this case they need to be referenced with the fully qualified name (``<module>.<symbol>``). A type can be an interface, struct, enum or flag. It is also possible to reference the inner members of the symbols with the fragment syntax (``<module>.<symbol>#<fragment>``).
-The QFace does not allow to extend interfaces. It is by design kept simple.
+A module consist of either one or more interfaces, structs and enums/flags. They can come in any number or combination. The interface is the only type which can contain properties, operations and signals. The struct is merely a container to transport structured data. An enum/flag allows the user to encode information used inside the struct or interface as data-type.
Below is an example of a QFace file.
@@ -153,7 +237,7 @@ More information on annotations can be found in the annotations chapter.
Comments
========
-Comments use the JavaDoc convention of using an `@` sign as prefix with the keyword followed by the required paramters.
+Comments use the JavaDoc convention of using an `@` sign as prefix with the keyword followed by the required parameters.
.. code-block::java
@@ -163,6 +247,6 @@ Comments use the JavaDoc convention of using an `@` sign as prefix with the keyw
Currently only brief, description, see and deprecated are supported doc tags.
-The QtCPP builtin generator generates valid Qt documentation out of these comments.
+The QtCPP built-in generator generates valid Qt documentation out of these comments.
diff --git a/qface/__about__.py b/qface/__about__.py
index c16b3dd..7073a0f 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.8"
+__version__ = "1.8.1"
__author__ = "JRyannel"
__author_email__ = "qface-generator@googlegroups.com"
__copyright__ = "2017 Pelagicore"
diff --git a/qface/generator.py b/qface/generator.py
index ef02062..eca36f0 100644
--- a/qface/generator.py
+++ b/qface/generator.py
@@ -35,6 +35,7 @@ logger = logging.getLogger(__name__)
Provides an API for accessing the file system and controlling the generator
"""
+
class ReportingErrorListener(ErrorListener.ErrorListener):
def __init__(self, document):
self.document = document
@@ -71,6 +72,7 @@ class Generator(object):
)
self.env.filters.update(filters)
self._destination = Path()
+ self._source = ''
self.context = context
@property
@@ -84,6 +86,16 @@ class Generator(object):
self._destination = Path(self.apply(dst, self.context))
@property
+ def source(self):
+ """source prefix for template lookup"""
+ return self._source
+
+ @source.setter
+ def source(self, source: str):
+ if source:
+ self._source = source
+
+ @property
def filters(self):
return self.env.filters
@@ -93,7 +105,14 @@ class Generator(object):
def get_template(self, name: str):
"""Retrieves a single template file from the template loader"""
- return self.env.get_template(name)
+ source = name
+ if name and name[0] is '/':
+ source = name[1:]
+ elif self.source is not None:
+ source = '/'.join((self.source, name))
+ print('get_template: ', name, source)
+
+ return self.env.get_template(source)
def render(self, name: str, context: dict):
"""Returns the rendered text from a single template file from the
@@ -115,7 +134,6 @@ class Generator(object):
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
@@ -161,25 +179,29 @@ class RuleGenerator(Generator):
self.context.update({
'dst': destination,
'project': Path(destination).name,
+ 'features': features,
})
self.destination = '{{dst}}'
self.features = features
- def process_rules(self, path: Path, system: System, features:set=set()):
+ def process_rules(self, path: Path, system: System):
"""writes the templates read from the rules document"""
- self.features.update(features)
- self.context.update({'system': system})
+ self.context.update({
+ 'system': system,
+ })
document = FileSystem.load_yaml(path, required=True)
for module, rules in document.items():
- click.secho('module: {0}'.format(module), fg='green')
+ click.secho('process: {0}'.format(module), fg='green')
self._process_rules(rules, system)
def _process_rules(self, rules: dict, system: System):
""" process a set of rules for a target """
+ self._source = None # reset the template source
if not self._shall_proceed(rules):
return
self.context.update(rules.get('context', {}))
self.destination = rules.get('destination', '{{dst}}')
+ self.source = rules.get('source', None)
self._process_rule(rules.get('system', None), {'system': system})
for module in system.modules:
self._process_rule(rules.get('module', None), {'module': module})
@@ -197,7 +219,10 @@ class RuleGenerator(Generator):
self.context.update(context)
self.context.update(rule.get('context', {}))
self.destination = rule.get('destination', None)
+ self.source = rule.get('source', None)
preserved = rule.get('preserve', [])
+ if not preserved:
+ preserved = []
for target, source in rule.get('documents', {}).items():
preserve = target in preserved
self.write(target, source, preserve=preserve)
@@ -263,6 +288,8 @@ class FileSystem(object):
"""Read a YAML document and for each root symbol identifier
updates the tag information of that symbol
"""
+ if not Path(document).exists():
+ return
meta = FileSystem.load_yaml(document)
click.secho('merge: {0}'.format(document.name), fg='blue')
for identifier, data in meta.items():
diff --git a/qface/helper/qtcpp.py b/qface/helper/qtcpp.py
index 5561fe5..83aa8fc 100644
--- a/qface/helper/qtcpp.py
+++ b/qface/helper/qtcpp.py
@@ -28,7 +28,7 @@ class Filters(object):
return 'QString()'
if t.is_real:
return 'qreal(0.0)'
- if t.is_variant:
+ if t.is_var:
return 'QVariant()'
elif t.is_void:
return ''
@@ -58,12 +58,16 @@ class Filters(object):
if symbol.type.is_enum:
return '{0}{1}Module::{2} {3}'.format(prefix, module_name, symbol.type, symbol)
if symbol.type.is_void or symbol.type.is_primitive:
- if symbol.type.name == 'string':
+ if symbol.type.is_string:
return 'const QString &{0}'.format(symbol)
- if symbol.type.name == 'var':
+ if symbol.type.is_var:
return 'const QVariant &{0}'.format(symbol)
- if symbol.type.name == 'real':
+ if symbol.type.is_real:
return 'qreal {0}'.format(symbol)
+ if symbol.type.is_bool:
+ return 'bool {0}'.format(symbol)
+ if symbol.type.is_int:
+ return 'int {0}'.format(symbol)
return '{0} {1}'.format(symbol.type, symbol)
elif symbol.type.is_list:
nested = Filters.returnType(symbol.type.nested)
@@ -82,16 +86,24 @@ class Filters(object):
def returnType(symbol):
prefix = Filters.classPrefix
module_name = upper_first(symbol.module.module_name)
- if symbol.type.is_enum:
+ t = symbol.type
+ if t.is_enum:
return '{0}{1}Module::{2}'.format(prefix, module_name, symbol.type)
if symbol.type.is_void or symbol.type.is_primitive:
- if symbol.type.name == 'string':
+ if t.is_string:
return 'QString'
- if symbol.type.name == 'var':
+ if t.is_var:
return 'QVariant'
- if symbol.type.name == 'real':
+ if t.is_real:
return 'qreal'
- return symbol.type.name
+ if t.is_int:
+ return 'int'
+ if t.is_bool:
+ return 'bool'
+ if t.is_void:
+ return 'void'
+ print(t)
+ assert False
elif symbol.type.is_list:
nested = Filters.returnType(symbol.type.nested)
return 'QVariantList'.format(nested)
@@ -125,7 +137,14 @@ class Filters(object):
@staticmethod
def ns(symbol):
'''generates a namespace x::y::z statement from a symbol'''
- return '::'.join(symbol.module.name_parts)
+ if symbol.type and symbol.type.is_primitive:
+ return ''
+ return '{0}::'.format('::'.join(symbol.module.name_parts))
+
+ @staticmethod
+ def fqn(symbol):
+ '''generates a fully qualified name from symbol'''
+ return '{0}::{1}'.format(Filters.ns(symbol), symbol.name)
@staticmethod
def signalName(s):
@@ -193,6 +212,7 @@ class Filters(object):
'close_ns': Filters.close_ns,
'using_ns': Filters.using_ns,
'ns': Filters.ns,
+ 'fqn': Filters.fqn,
'signalName': Filters.signalName,
'parameters': Filters.parameters,
'signature': Filters.signature,
diff --git a/qface/idl/domain.py b/qface/idl/domain.py
index defdca3..d4f87ca 100644
--- a/qface/idl/domain.py
+++ b/qface/idl/domain.py
@@ -220,6 +220,11 @@ class TypeSymbol(NamedElement):
return self.is_primitive and self.name == 'string'
@property
+ def is_var(self):
+ '''checks if type is primitive and var'''
+ return self.is_primitive and self.name == 'var'
+
+ @property
def is_enumeration(self):
'''checks if type is complex and insytance of type Enum'''
return self.is_complex and isinstance(self.reference, Enum)
@@ -245,11 +250,6 @@ class TypeSymbol(NamedElement):
return self.is_complex and isinstance(self.reference, Interface)
@property
- def is_variant(self):
- '''checks if type is primitive and string'''
- return self.is_primitive and self.name == 'var'
-
- @property
def reference(self):
"""returns the symbol reference of the type name"""
if not self.__is_resolved:
diff --git a/qface/templates/qface/qtcpp.j2 b/qface/templates/qface/qtcpp.j2
index bf7824f..c2b13db 100644
--- a/qface/templates/qface/qtcpp.j2
+++ b/qface/templates/qface/qtcpp.j2
@@ -30,7 +30,7 @@ void {{symbol}}{{postfix}}({{symbol|parameters}});
{% macro property_setter_impl(class, property) -%}
/*!
- \qmlproperty {{property.type}} {{interface}}::{{property}}
+ \qmlproperty {{property.type}} {{class}}::{{property}}
{% with doc = property.comment|parse_doc %}
\brief {{doc.brief}}
@@ -42,7 +42,7 @@ void {{class}}::set{{property|upperfirst}}({{ property|parameterType }})
{
if (m_{{property}} != {{property}}) {
m_{{property}} = {{property}};
- emit {{property}}Changed({{property}});
+ Q_EMIT {{property}}Changed({{property}});
}
}
{%- endmacro %}
diff --git a/qface/watch.py b/qface/watch.py
index f99b45a..4667fe3 100644
--- a/qface/watch.py
+++ b/qface/watch.py
@@ -42,7 +42,6 @@ def monitor(script, src, dst):
click.secho('watch recursive: {0}'.format(script.dirname()), fg='blue')
observer.schedule(event_handler, script.dirname(), recursive=True)
for entry in src:
- entry = entry.dirname().expand().abspath()
click.secho('watch recursive: {0}'.format(entry), fg='blue')
observer.schedule(event_handler, entry, recursive=True)
event_handler.run() # run always once