diff options
author | Dominik Holland <dominik.holland@qt.io> | 2019-10-17 14:52:47 +0200 |
---|---|---|
committer | Dominik Holland <dominik.holland@qt.io> | 2019-10-30 12:08:13 +0100 |
commit | 30e611e7362cc42831e437aff11aae280e6a996f (patch) | |
tree | 73f4f67defa69a837d38618d5835329e5f7fda49 | |
parent | 25ba2939cace6bfbc065f3c8ea8cfe6d72eb5c17 (diff) |
ivigenerator: Split the generator into several files
The generate.py file has been growing a lot over the time, splitting
it into several files, should make it easier to maintain and
understand the code.
Change-Id: I2cdff2c5875c4a3f287002b0cd05ab9094222cbf
Reviewed-by: Robert Griebl <robert.griebl@qt.io>
-rwxr-xr-x | src/tools/ivigenerator/generate.py | 716 | ||||
-rw-r--r-- | src/tools/ivigenerator/generator/builtin_config.py | 54 | ||||
-rw-r--r-- | src/tools/ivigenerator/generator/filters.py | 699 | ||||
-rw-r--r-- | src/tools/ivigenerator/generator/global_functions.py | 87 | ||||
-rw-r--r-- | src/tools/ivigenerator/ivigenerator.pro | 10 |
5 files changed, 858 insertions, 708 deletions
diff --git a/src/tools/ivigenerator/generate.py b/src/tools/ivigenerator/generate.py index e2b0b6c..db05e0d 100755 --- a/src/tools/ivigenerator/generate.py +++ b/src/tools/ivigenerator/generate.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# Copyright (C) 2019 The Qt Company Ltd. # Copyright (C) 2019 Luxoft Sweden AB # Copyright (C) 2018 Pelagicore AG # Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB) @@ -43,691 +44,28 @@ import os import click import logging.config import yaml -import json import sys from path import Path from qface.generator import FileSystem, Generator -from qface.helper.qtcpp import Filters from qface.watch import monitor from qface.idl.domain import Module, Interface, Property, Parameter, Field, Struct -from qface.helper.generic import upper_first from jinja2 import TemplateAssertionError import inspect +import generator.builtin_config as builtin_config +from generator.global_functions import register_global_functions +from generator.filters import register_filters + here = Path(__file__).dirname() log = logging.getLogger(__file__) -Filters.classPrefix = '' currentQFaceSrcFile = '' -builtin_config = {} IVI_DEFAULT_TEMPLATES = ['frontend', 'qmlplugin', 'backend_simulator', 'backend_qtro', 'server_qtro', 'server_qtro_simulator', 'test'] -def jinjaTrace(): - """ - Collects all jinja template files and the line numbers from the current calltrace - """ - frame = inspect.currentframe() - infos = [] - while frame: - template = frame.f_globals.get('__jinja_template__') - if template is not None: - infos.append((inspect.getsourcefile(frame), template.get_corresponding_lineno(frame.f_lineno))) - frame = frame.f_back - - return infos - -def jinja_error(msg): - """ - Throws an error for the current jinja template and the templates this is included from - This can be used inside a filter to indicate problems with the passed arguments or direclty inside - an template - """ - infos = jinjaTrace() - if len(infos): - message = msg - if len(infos) > 1: - for info in infos[1:]: - message = message + "\n{0}:{1}: instantiated from here".format(info[0], info[1]) - message = message + "\n{0}: instantiated from here".format(currentQFaceSrcFile) - raise TemplateAssertionError(message, infos[0][1], "", infos[0][0]) - raise TemplateAssertionError(msg, -1, "", "unknown") - -def jinja_warning(msg): - """ - Reports an warning in the current jinja template. - This can be used inside a filter to indicate problems with the passed arguments or direclty inside - an template - """ - file, lineno = jinjaTrace()[0] - if file: - message = '{0}:{1}: warning: {2}'.format(file, lineno, msg) - else: - message = '<unknown-file>: warning: {0}'.format(msg) - click.secho(message, fg='yellow', err=True) - -def tag_by_path(symbol, path, default_value=False): - """ - Find the tag given by its full path in the object hierarchy, - like "property.config_sim.zones.right". If some part of the - path is missing, return None - """ - path_parts = path.split(".") - cur_level_obj = symbol.tags - for path_part in path_parts: - if path_part in cur_level_obj: - cur_level_obj = cur_level_obj[path_part] - else: - cur_level_obj = None - break - if cur_level_obj is None: - cur_level_obj = default_value - return cur_level_obj - - -def conf_sim_tag(symbol, path, default_value=False): - """ - Find tag, given by its path, located under "config_simulator" sub-object. - Returns None, of any of the path parts is missing - """ - return tag_by_path(symbol, "config_simulator." + path, default_value) - - -def enum_value_to_cppliteral(value, module_name): - value = value.strip().rsplit('.', 1)[-1] - return '{0}{1}Module::{2}'.format(Filters.classPrefix, upper_first(module_name), value) - - -def enum_value(value, module_name): - sub_values = value.split('|') - sub_values = [enum_value_to_cppliteral(v, module_name) for v in sub_values] - return "|".join(sub_values) - -def default_type_value(symbol): - """ - Find the default value for the type. Models are initialized as nullptr - """ - prefix = Filters.classPrefix - t = symbol.type # type: qface.domain.TypeSymbol - if t.is_primitive: - if t.is_int: - return 'int(0)' - if t.is_bool: - return 'bool(false)' - if t.is_string: - return 'QString()' - if t.is_real: - return 'qreal(0.0)' - if t.is_var: - return 'QVariant()' - elif t.is_void: - return '' - elif t.is_enum: - module_name = t.reference.module.module_name - value = next(iter(t.reference.members)) - return '{0}{1}Module::{2}'.format(prefix, upper_first(module_name), value) - elif t.is_flag: - module_name = t.reference.module.module_name - return '{0}{1}Module::{2}()'.format(prefix, upper_first(module_name), flag_type(symbol)) - elif symbol.type.is_list: - nested = Filters.returnType(symbol.type.nested) - return 'QVariantList()'.format(nested) - elif symbol.type.is_struct: - return '{0}{1}()'.format(prefix, symbol.type.reference.name) - elif symbol.type.is_model: - return 'nullptr' - jinja_error('default_type_value: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) - -def test_type_value(symbol): - """ - Find a value different than the default value for the type. Models are initialized as nullptr - """ - prefix = Filters.classPrefix - t = symbol.type # type: qface.domain.TypeSymbol - if t.is_primitive: - if t.is_int: - return '111' - if t.is_bool: - return 'true' - if t.is_string: - return '"TEST STRING"' - if t.is_real: - return '1234.5678' - if t.is_var: - return 'QVariant("TEST VARIANT")' - elif t.is_void: - return '' - elif t.is_enum: - module_name = t.reference.module.module_name - value = list(iter(t.reference.members))[-1] - return '{0}{1}Module::{2}'.format(prefix, upper_first(module_name), value) - elif t.is_flag: - module_name = t.reference.module.module_name - value = next(iter(t.reference.members)) - return '{0}{1}Module::{2}'.format(prefix, upper_first(module_name), value) - elif symbol.type.is_list: - value = test_type_value(t.nested.type) - if not (t.nested.type.is_primitive ): - value = 'QVariant::fromValue({0})'.format(value) - return 'QVariantList({{{0}}})'.format(value) - elif symbol.type.is_struct: - values_string = ', '.join(test_type_value(e) for e in symbol.type.reference.fields) - return '{0}{1}({2})'.format(prefix, symbol.type.reference.name, values_string) - elif symbol.type.is_model: - return 'new QIviPagingModel()' - jinja_error('test_type_value: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) - -def default_value_helper(symbol, res): - t = symbol.type - if t.is_struct: - if not (isinstance(res, dict) or isinstance(res, list)): - jinja_error('default_value: value in annotation is supposed to be a dict or list') - if len(res) != len(t.reference.fields): - jinja_error('default_value: argument count in annotation and number of struct fields does not match') - values_string = ', '.join(default_value_helper(list(t.reference.fields)[idx], property) for idx, property in enumerate(res)) - return '{0}({{{1}}})'.format(t.type, values_string) - if t.is_model or t.is_list: - if not isinstance(res, list): - jinja_error('default_value: value in annotation is supposed to be a list') - row_string = '' - if t.nested.is_struct and t.is_list: - row_string = ', '.join(('QVariant::fromValue({0})'.format(default_value_helper(t.nested, row))) for row in res) - else: - row_string = ', '.join(default_value_helper(t.nested, row) for row in res) - return '{{{0}}}'.format(row_string) - if t.is_enum or t.is_flag: - module_name = t.reference.module.module_name - return enum_value(res, module_name) - # in case it's bool, Python True is sent to the C++ as "True", let's take care of that - if t.is_bool: - if res: - return 'true' - else: - return 'false' - if t.is_string: - return 'QLatin1String("{0}")'.format(res.replace("\\", "\\\\")) - if t.is_var: - if isinstance(res, str): - res = 'QLatin1String("{0}")'.format(res) - return 'QVariant({0})'.format(res) - - return '{0}'.format(res) - -def default_value(symbol, zone='='): - """ - Find the default value used by the simulator backend - """ - res = default_type_value(symbol) - if symbol.type.is_model: - res = '{}'; - if 'config_simulator' in symbol.tags and 'default' in symbol.tags['config_simulator']: - res = symbol.tags['config_simulator']['default'] - if isinstance(res, dict): - if zone in res: - res = res[zone] - elif '=' in res: - res = res['='] - return default_value_helper(symbol, res) - - return res - -def parameter_type_default(symbol): - """ - Return the parameter declaration for properties, handle camel case module name - """ - prefix = Filters.classPrefix - if symbol.type.is_enum or symbol.type.is_flag: - return '{0}{1}Module::{2} {3}={4}'.format(prefix, upper_first(symbol.type.reference.module.module_name), flag_type(symbol), symbol, default_type_value(symbol)) - if symbol.type.is_void or symbol.type.is_primitive: - if symbol.type.name == 'string': - return 'const QString &{0}=QString()'.format(symbol) - if symbol.type.name == 'var': - return 'const QVariant &{0}=QVariant()'.format(symbol) - if symbol.type.name == 'real': - return 'qreal {0}=qreal()'.format(symbol) - return '{0} {1}={2}'.format(symbol.type, symbol, default_type_value(symbol)) - elif symbol.type.is_list: - nested = return_type(symbol.type.nested) - return 'const QVariantList &{1}=QVariantList()'.format(nested, symbol) - elif symbol.type.is_model: - nested = symbol.type.nested - if nested.is_primitive: - return '{0}VariantModel *{1}=QVariantModel'.format(prefix, symbol) - elif nested.is_complex: - return 'QIviPagingModel *{0}=nullptr'.format(symbol) - else: - return 'const {0}{1} &{2}={0}{1}()'.format(prefix, symbol.type.reference.name, symbol) - jinja_error('parameter_type_default: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) - -def parameter_type(symbol): - """ - Return the parameter declaration for properties, handle camel case module name - """ - prefix = Filters.classPrefix - if symbol.type.is_enum or symbol.type.is_flag: - return '{0}{1}Module::{2} {3}'.format(prefix, upper_first(symbol.type.reference.module.module_name), flag_type(symbol), symbol) - if symbol.type.is_void or symbol.type.is_primitive: - if symbol.type.name == 'string': - return 'const QString &{0}'.format(symbol) - if symbol.type.name == 'var': - return 'const QVariant &{0}'.format(symbol) - if symbol.type.name == 'real': - return 'qreal {0}'.format(symbol) - return '{0} {1}'.format(symbol.type, symbol) - elif symbol.type.is_list: - nested = return_type(symbol.type.nested) - return 'const QVariantList &{1}'.format(nested, symbol) - elif symbol.type.is_model: - nested = symbol.type.nested - if nested.is_primitive: - return '{0}VariantModel *{1}'.format(prefix, symbol) - elif nested.is_complex: - return 'QIviPagingModel *{0}'.format(symbol) - else: - return 'const {0}{1} &{2}'.format(prefix, symbol.type.reference.name, symbol) - jinja_error('parameter_type: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) - - -def return_type(symbol): - """ - Return the type declaration for properties, handle camel case module name - """ - prefix = Filters.classPrefix - if symbol.type.is_enum or symbol.type.is_flag: - return('{0}{1}Module::{2}'.format(prefix, upper_first(symbol.type.reference.module.module_name), flag_type(symbol))) - if symbol.type.is_void or symbol.type.is_primitive: - if symbol.type.name == 'string': - return 'QString' - if symbol.type.name == 'var': - return 'QVariant' - if symbol.type.name == 'real': - return 'qreal' - return symbol.type.name - elif symbol.type.is_list: - nested = return_type(symbol.type.nested) - return 'QVariantList'.format(nested) - elif symbol.type.is_model: - nested = symbol.type.nested - if nested.is_primitive: - return '{0}VariantModel *'.format(prefix) - elif nested.is_complex: - return 'QIviPagingModel *' - else: - return '{0}{1}'.format(prefix, symbol.type.reference.name) - jinja_error('return_type: Unknown symbol {0} of type {1}'.format(symbol, symbol.type)) - - -def flag_type(symbol): - """ - Return the annotation for the flag type if available, the plural otherwise - """ - actualType = symbol - if symbol.type.reference: - actualType = symbol.type.reference - if actualType.is_flag: - if 'config' in actualType.tags and 'type' in actualType.tags['config']: - return actualType.tags['config']['type'] - return '{0}s'.format(actualType) - return actualType - - -def domain_values(symbol): - """ - Returns domain values for property (if defined by @domain) - """ - if type(symbol) is Property: - if 'config_simulator' in symbol.tags: - if 'domain' in symbol.tags['config_simulator']: - return symbol.tags['config_simulator']['domain'] - return None - -def getter_name(symbol): - """ - Returns the getter name of the property - """ - if type(symbol) is Property: - if 'config' in symbol.tags and 'getter_name' in symbol.tags['config']: - return symbol.tags['config']['getter_name'] - return symbol - -def setter_name(symbol): - """ - Returns the setter name of the property - """ - if type(symbol) is Property: - if 'config' in symbol.tags and 'setter_name' in symbol.tags['config']: - return symbol.tags['config']['setter_name'] - return 'set' + symbol.name[0].upper() + symbol.name[1:] - -def range_value(symbol, index, key): - """ - Returns value for property (if defined by @range index or key) - """ - if type(symbol) is Property and symbol.type.is_int or symbol.type.is_real: - if 'config_simulator' in symbol.tags: - if 'range' in symbol.tags['config_simulator']: - return symbol.tags['config_simulator']['range'][index] - if key in symbol.tags['config_simulator']: - return symbol.tags['config_simulator'][key] - return None - - -def range_high(symbol): - """ - Returns maximum value for property (if defined by @range or @maximum) - """ - return range_value(symbol, 1, 'maximum') - - -def range_low(symbol): - """ - Returns minimum value for property (if defined by @range or @minimum) - """ - return range_value(symbol, 0, 'minimum') - - -def has_domains(properties): - """ - Returns true if any property has range or domain tags - """ - for property in properties: - if 'config_simulator' in property.tags: - for p in ['range', 'domain', 'minimum', 'maximum']: - if p in property.tags['config_simulator']: - return True - return False - -def strip_QT(s): - """ - If the given string starts with QT, stip it away. - """ - s = str(s) - if s.startswith('QT'): - return s[2:] - return s - -def json_domain(properties): - """ - Returns property domains formated in json - """ - data = {} - if len(properties): - data["iviVersion"] = builtin_config["VERSION"] - for property in properties: - if 'config_simulator' in property.tags: - for p in ['range', 'domain', 'minimum', 'maximum']: - if property.tags['config_simulator'] is not None and p in property.tags['config_simulator']: - if not property.name in data: - data[property.name] = {} - data[property.name][p] = property.tags['config_simulator'][p] - return json.dumps(data, separators=(',', ':')) - -def simulationData(module): -# if type(module) is not Module: -# return - - found = False - data = {} - for interface in module.interfaces: - iData = {} - if 'config_simulator' in interface.tags: - iData = interface.tags['config_simulator'] - for property in interface.properties: - if 'config_simulator' in property.tags: - for p in ['range', 'domain', 'minimum', 'maximum', 'default']: - if property.tags['config_simulator'] is not None and p in property.tags['config_simulator']: - if not property.name in iData: - iData[property.name] = {} - iData[property.name][p] = symbolToJson(property.tags['config_simulator'][p], property.type) - data[interface.name] = iData - return json.dumps(data, indent=' ') - -def symbolToJson(data, symbol): - if symbol.type.is_struct: - t = symbol.type - if not (isinstance(data, dict) or isinstance(data, list)): - jinja_error('simulationData: value in annotation is supposed to be a dict or list') - if len(data) != len(t.reference.fields): - jinja_error('simulationData: argument count in annotation and number of struct fields does not match') - newList = list(symbolToJson(property, list(t.reference.fields)[idx]) for idx, property in enumerate(data)) - return { "type": symbol.type.name, "value": newList } - elif symbol.type.is_enum or symbol.type.is_flag: - module_name = symbol.type.reference.module.module_name - return { "type": "enum", "value": enum_value(data, module_name) } - elif symbol.type.is_list or symbol.type.is_model: - nested = symbol.type.nested - if nested.is_complex: - newList = [] - for value in data: - newList.append(symbolToJson(value, nested)) - return newList - return data - -def qml_control_properties(symbol, backend_object): - """ - Returns properties of the QML control matching to this - IDL type (e.g. min/max properties) - """ - prop_str = lower_first_filter(symbol) + "Control" - if isinstance(symbol, Property): - top = range_high(symbol) - bottom = range_low(symbol) - binding = "value: {0}.{1};".format(backend_object, symbol.name) - if top is not None and bottom is not None: - return 'id: {0}; from: {1}; to: {2}; {3}'.format(prop_str, bottom, top, binding) - - if top is not None or bottom is not None: - if top is None: - return 'id: {0}; from: {1}; to:100000; {2}'.format(prop_str, bottom, binding) - elif bottom is None: - return 'id: {0}; from:-100000; to: {1}; {2}'.format(prop_str, top, binding) - - values = domain_values(symbol) - if values is None and (symbol.type.is_enum or symbol.type.is_flag): - values_string = ' '.join('ListElement {{ key: "{0}"; value: {1}.{0} }}'.format(e, qml_type(symbol.interface)) for e in symbol.type.reference.members) - return 'id: {0}; textRole: "key"; {2} model: ListModel {{ {1} }}'.format(prop_str, values_string, binding) - if values is not None: - values_string = ','.join('"'+str(e)+'"' for e in values) - return 'id: {0}; model: [ {1} ]; '.format(prop_str, values_string) - if symbol.type.is_bool: - binding = "checked: {0}.{1};".format(backend_object, symbol.name) - return 'id: {0}; {1}'.format(prop_str, binding) - if symbol.type.is_real or symbol.type.is_int or symbol.type.is_string: - binding = "text: {0}.{1};".format(backend_object, symbol.name) - return 'id: {0}; {1}'.format(prop_str, binding) - - if isinstance(symbol, Parameter): - return 'id: {1}Param{0}'.format(prop_str, symbol.operation) - if isinstance(symbol, Field): - return 'id: {1}_{0}'.format(prop_str, lower_first_filter(symbol.struct)) - -def qml_control_signal_parameters(symbol): - """ - Returns the parameters for calling the signal using the values from the ui controls - """ - return ', '.join('{0}Param{1}Control.{2}'.format(e.operation, lower_first_filter(e), qml_binding_property(e)) for e in symbol.parameters) - -def qml_meta_control_name(symbol): - """ - Returns name of the QML control needed to display this type based on the meta - data of the symbol -- if symbol has some meta data (e.g. value limits or domain) - then control name is taken based on these constraints. Otherwise returns None. - """ - top = range_high(symbol) - bottom = range_low(symbol) - if top is not None and bottom is not None: - return 'Slider' - - if top is not None or bottom is not None: - return 'SpinBox' - - values = domain_values(symbol) - if values is not None: - return "ComboBox" - -def qml_type_control_name(symbol): - """ - Returns name of the QML control inferred based on the type of the symbol. - """ - t = symbol.type - if t.is_string or t.is_int or t.is_real: - return "TextField" - elif t.is_bool: - return "CheckBox" - elif t.is_enum: - if t.reference.is_enum: - return "EnumControl" - elif t.reference.is_flag: - return "FlagControl" - elif t.is_flag: - return "FlagControl" - return "TextField" - - -def qml_control_name(symbol): - """ - Returns name of the QML control for the symbol. First it checks meta data (as it may - influence the control type) and if nothing is defined there, it falls back to the - symbol actual type. - """ - # First try to calculate control name based on the tags - control_name = qml_meta_control_name(symbol) - # If nothing is defined, calculate it based on its type - if control_name is None: - control_name = qml_type_control_name(symbol) - return control_name - - -def qml_control(symbol, backend_object): - """ - Returns QML code for the control (or group of controls) to represent the editing UI for the symbol. - """ - - if symbol.type.is_struct: - return qml_struct_control(symbol) - - return "{0} {{ {1} }}".format(qml_control_name(symbol), qml_control_properties(symbol, backend_object)) - - -def qml_binding_property(symbol): - """ - :param symbol: property which is being bound by the control - :return: name of the property of the QML control to be bound with - """ - control_name = qml_control_name(symbol) - if control_name == "CheckBox": - return "checked" - elif control_name == "Slider" or control_name == "SpinBox" or control_name == "FlagControl" or control_name == "EnumControl": - return "value" - elif control_name == "TextField": - return "text" - elif control_name == "ComboBox": - return "currentIndex" - -def qml_struct_control(symbol): - if symbol.type.is_struct and symbol.type.reference.fields: - result = "Rectangle { ColumnLayout { " - for field in symbol.type.reference.fields: - result += qml_control(field) - result += "}}" - return result - - -def qml_info_type(symbol): - """ - Returns the correct type for the symbol, to be used inside the qmltype templates - """ - prefix = Filters.classPrefix - if symbol.type.is_enum or symbol.type.is_flag: - return('{0}{1}Module::{2}'.format(prefix, upper_first(symbol.module.module_name), flag_type(symbol))) - elif symbol.type.is_void or symbol.type.is_primitive: - if symbol.type.is_real: - return 'double' - return symbol.type.name - elif symbol.type.is_struct: - return 'QVariant' - elif symbol.type.is_list: - return 'QVariantList' - elif symbol.type.is_model: - return 'QIviPagingModel' - else: - jinja_error('qml_info_type: Unknown symbol {0} of type {1}'.format(symbol, symbol.type)) - -def qml_type(symbol): - """ - :param interface: - :return: Returns the name of the interface for using in QML. This name is defined in the IDL under - the "config" tag as "qml_type". This annotation is optional, if not provided, the interface name is - used. - """ - result = symbol.name - if 'qml_type' in symbol.tags['config']: - result = symbol.tags['config']['qml_type'] - elif 'qml_name' in symbol.tags['config']: - result = symbol.tags['config']['qml_name'] - return result - -def model_type(symbol): - if symbol.type.is_model: - nested = symbol.type.nested - return '{0}Model'.format(nested) - return None - - -def struct_includes(symbol): - includesSet = set() - tpl = '#include \"{0}.h\"' - - if isinstance(symbol, Struct): - for val in symbol.fields: - if val.type.is_struct: - includesSet.add(tpl.format(val.type.reference.name).lower()) - elif isinstance(symbol, Interface): - for val in symbol.properties: - if val.type.is_struct: - includesSet.add(tpl.format(val.type.reference.name).lower()) - for op in symbol.operations: - for param in op.parameters: - if param.type.is_struct: - includesSet.add(tpl.format(param.type.reference.name).lower()) - if op.type.is_struct: - includesSet.add(tpl.format(op.type.reference.name).lower()) - for op in symbol.signals: - for param in op.parameters: - if param.type.is_struct: - includesSet.add(tpl.format(param.type.reference.name).lower()) - - return includesSet - -def comment_text(comment): - """ - Returns the text of the passed comment without the leading/trailing comment tokens e.g. /**, * - """ - comment_start = [ '/**', '/*!', '/*'] - processed = [] - isComment = False - - # No comment is NOT a error - if len(comment) == 0: - return processed - - for token in comment_start: - if (comment.startswith(token)): - isComment = True - break; - if isComment: - comment = comment[3:-2] - else: - jinja_error("comment_text: The provided comment needs to be start with one of these strings: {}".format(comment_start)) - - for line in comment.splitlines(): - line = line.lstrip(" *") - processed.append(line) - return processed - def generate(tplconfig, moduleConfig, annotations, imports, src, dst): log.debug('run {0} {1}'.format(src, dst)) FileSystem.strict = True @@ -751,45 +89,16 @@ def generate(tplconfig, moduleConfig, annotations, imports, src, dst): exit(1) FileSystem.merge_annotations(system, Path(annotations_file)) generator = Generator(search_path=[tplconfig, here]) - generator.env.keep_trailing_newline = True - generator.register_filter('return_type', return_type) - generator.register_filter('parameter_type_default', parameter_type_default) - generator.register_filter('parameter_type', parameter_type) - generator.register_filter('getter_name', getter_name) - generator.register_filter('setter_name', setter_name) - generator.register_filter('test_type_value', test_type_value) - generator.register_filter('default_type_value', default_type_value) - generator.register_filter('default_value', default_value) - generator.register_filter('model_type', model_type) - generator.register_filter('flag_type', flag_type) - generator.register_filter('range_low', range_low) - generator.register_filter('range_high', range_high) - generator.register_filter('strip_QT', strip_QT) - generator.register_filter('domain_values', domain_values) - generator.register_filter("enum_value", enum_value) - generator.register_filter("tag_by_path", tag_by_path) - generator.register_filter("conf_sim_tag", conf_sim_tag) - generator.register_filter('has_domains', has_domains) - generator.register_filter('simulationData', simulationData) - generator.register_filter('json_domain', json_domain) - generator.register_filter('qml_info_type', qml_info_type) - generator.register_filter('qml_type', qml_type) - generator.register_filter('qml_control', qml_control) - generator.register_filter('qml_binding_property', qml_binding_property) - generator.register_filter('qml_control_signal_parameters', qml_control_signal_parameters) - generator.register_filter('struct_includes', struct_includes) - generator.register_filter('comment_text', comment_text) - #Register global functions - generator.env.globals["error"] = jinja_error - generator.env.globals["warning"] = jinja_warning + register_global_functions(generator) + register_filters(generator) srcFile = os.path.basename(src[0]) srcBase = os.path.splitext(srcFile)[0] global currentQFaceSrcFile currentQFaceSrcFile = src[0] - ctx = {'dst': dst, 'qtASVersion': builtin_config["VERSION"], 'srcFile':srcFile, 'srcBase':srcBase, 'features': builtin_config["FEATURES"]} + ctx = {'dst': dst, 'qtASVersion': builtin_config.config["VERSION"], 'srcFile':srcFile, 'srcBase':srcBase, 'features': builtin_config.config["FEATURES"]} gen_config = yaml.load(open(os.path.dirname(tplconfig) + '/{0}.yaml'.format(os.path.basename(tplconfig))), Loader=yaml.SafeLoader) #Make sure the config tag is available for all our symbols @@ -892,13 +201,8 @@ def app(src, dst, format, reload, module, force, annotations, imports): in the given dst directory. """ - global builtin_config - builtin_config_path = here / '.config' - if 'IVIGENERATOR_CONFIG' in os.environ: - builtin_config_path = os.environ['IVIGENERATOR_CONFIG'] - builtin_config = yaml.load(open(builtin_config_path), Loader=yaml.SafeLoader) - if not 'VERSION' in builtin_config or not 'FEATURES' in builtin_config: - sys.exit("Invalid builtin config") + # Parse the .config file and throw an error in case it doesn't exist or it is invalid + builtin_config.parse(here) if reload: script = '{0} {1} {2}'.format(Path(__file__).abspath(), ' '.join(src), dst) diff --git a/src/tools/ivigenerator/generator/builtin_config.py b/src/tools/ivigenerator/generator/builtin_config.py new file mode 100644 index 0000000..efa6c67 --- /dev/null +++ b/src/tools/ivigenerator/generator/builtin_config.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019 The Qt Company Ltd. +# Copyright (C) 2019 Luxoft Sweden AB +# Copyright (C) 2018 Pelagicore AG +# Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB) +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the QtIvi module of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:LGPL-QTAS$ +# Commercial License Usage +# Licensees holding valid commercial Qt Automotive Suite licenses may use +# this file in accordance with the commercial license agreement provided +# with the Software or, alternatively, in accordance with the terms +# contained in a written agreement between you and The Qt Company. For +# licensing terms and conditions see https://www.qt.io/terms-conditions. +# For further information use the contact form at https://www.qt.io/contact-us. +# +# GNU Lesser General Public License Usage +# Alternatively, this file may be used under the terms of the GNU Lesser +# General Public License version 3 as published by the Free Software +# Foundation and appearing in the file LICENSE.LGPL3 included in the +# packaging of this file. Please review the following information to +# ensure the GNU Lesser General Public License version 3 requirements +# will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 2.0 or (at your option) the GNU General +# Public license version 3 or any later version approved by the KDE Free +# Qt Foundation. The licenses are as published by the Free Software +# Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-2.0.html and +# https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +# SPDX-License-Identifier: LGPL-3.0 + +import yaml +import os + +config = {} + +def parse(here): + global config + builtin_config_path = here / '.config' + if 'IVIGENERATOR_CONFIG' in os.environ: + builtin_config_path = os.environ['IVIGENERATOR_CONFIG'] + config = yaml.load(open(builtin_config_path), Loader=yaml.SafeLoader) + if not 'VERSION' in config or not 'FEATURES' in config: + sys.exit("Invalid builtin config") diff --git a/src/tools/ivigenerator/generator/filters.py b/src/tools/ivigenerator/generator/filters.py new file mode 100644 index 0000000..3fd4a8b --- /dev/null +++ b/src/tools/ivigenerator/generator/filters.py @@ -0,0 +1,699 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019 The Qt Company Ltd. +# Copyright (C) 2019 Luxoft Sweden AB +# Copyright (C) 2018 Pelagicore AG +# Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB) +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the QtIvi module of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:LGPL-QTAS$ +# Commercial License Usage +# Licensees holding valid commercial Qt Automotive Suite licenses may use +# this file in accordance with the commercial license agreement provided +# with the Software or, alternatively, in accordance with the terms +# contained in a written agreement between you and The Qt Company. For +# licensing terms and conditions see https://www.qt.io/terms-conditions. +# For further information use the contact form at https://www.qt.io/contact-us. +# +# GNU Lesser General Public License Usage +# Alternatively, this file may be used under the terms of the GNU Lesser +# General Public License version 3 as published by the Free Software +# Foundation and appearing in the file LICENSE.LGPL3 included in the +# packaging of this file. Please review the following information to +# ensure the GNU Lesser General Public License version 3 requirements +# will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 2.0 or (at your option) the GNU General +# Public license version 3 or any later version approved by the KDE Free +# Qt Foundation. The licenses are as published by the Free Software +# Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-2.0.html and +# https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +# SPDX-License-Identifier: LGPL-3.0 + +import json + +from qface.idl.domain import Module, Interface, Property, Parameter, Field, Struct +from qface.helper.generic import upper_first +from qface.helper.qtcpp import Filters + +from .global_functions import jinja_error, jinja_warning +from . import builtin_config + +def tag_by_path(symbol, path, default_value=False): + """ + Find the tag given by its full path in the object hierarchy, + like "property.config_sim.zones.right". If some part of the + path is missing, return None + """ + path_parts = path.split(".") + cur_level_obj = symbol.tags + for path_part in path_parts: + if path_part in cur_level_obj: + cur_level_obj = cur_level_obj[path_part] + else: + cur_level_obj = None + break + if cur_level_obj is None: + cur_level_obj = default_value + return cur_level_obj + + +def conf_sim_tag(symbol, path, default_value=False): + """ + Find tag, given by its path, located under "config_simulator" sub-object. + Returns None, of any of the path parts is missing + """ + return tag_by_path(symbol, "config_simulator." + path, default_value) + + +def enum_value_to_cppliteral(value, module_name): + value = value.strip().rsplit('.', 1)[-1] + return '{0}{1}Module::{2}'.format(Filters.classPrefix, upper_first(module_name), value) + + +def enum_value(value, module_name): + sub_values = value.split('|') + sub_values = [enum_value_to_cppliteral(v, module_name) for v in sub_values] + return "|".join(sub_values) + +def default_type_value(symbol): + """ + Find the default value for the type. Models are initialized as nullptr + """ + prefix = Filters.classPrefix + t = symbol.type # type: qface.domain.TypeSymbol + if t.is_primitive: + if t.is_int: + return 'int(0)' + if t.is_bool: + return 'bool(false)' + if t.is_string: + return 'QString()' + if t.is_real: + return 'qreal(0.0)' + if t.is_var: + return 'QVariant()' + elif t.is_void: + return '' + elif t.is_enum: + module_name = t.reference.module.module_name + value = next(iter(t.reference.members)) + return '{0}{1}Module::{2}'.format(prefix, upper_first(module_name), value) + elif t.is_flag: + module_name = t.reference.module.module_name + return '{0}{1}Module::{2}()'.format(prefix, upper_first(module_name), flag_type(symbol)) + elif symbol.type.is_list: + nested = Filters.returnType(symbol.type.nested) + return 'QVariantList()'.format(nested) + elif symbol.type.is_struct: + return '{0}{1}()'.format(prefix, symbol.type.reference.name) + elif symbol.type.is_model: + return 'nullptr' + jinja_error('default_type_value: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) + +def test_type_value(symbol): + """ + Find a value different than the default value for the type. Models are initialized as nullptr + """ + prefix = Filters.classPrefix + t = symbol.type # type: qface.domain.TypeSymbol + if t.is_primitive: + if t.is_int: + return '111' + if t.is_bool: + return 'true' + if t.is_string: + return '"TEST STRING"' + if t.is_real: + return '1234.5678' + if t.is_var: + return 'QVariant("TEST VARIANT")' + elif t.is_void: + return '' + elif t.is_enum: + module_name = t.reference.module.module_name + value = list(iter(t.reference.members))[-1] + return '{0}{1}Module::{2}'.format(prefix, upper_first(module_name), value) + elif t.is_flag: + module_name = t.reference.module.module_name + value = next(iter(t.reference.members)) + return '{0}{1}Module::{2}'.format(prefix, upper_first(module_name), value) + elif symbol.type.is_list: + value = test_type_value(t.nested.type) + if not (t.nested.type.is_primitive ): + value = 'QVariant::fromValue({0})'.format(value) + return 'QVariantList({{{0}}})'.format(value) + elif symbol.type.is_struct: + values_string = ', '.join(test_type_value(e) for e in symbol.type.reference.fields) + return '{0}{1}({2})'.format(prefix, symbol.type.reference.name, values_string) + elif symbol.type.is_model: + return 'new QIviPagingModel()' + jinja_error('test_type_value: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) + +def default_value_helper(symbol, res): + t = symbol.type + if t.is_struct: + if not (isinstance(res, dict) or isinstance(res, list)): + jinja_error('default_value: value in annotation is supposed to be a dict or list') + if len(res) != len(t.reference.fields): + jinja_error('default_value: argument count in annotation and number of struct fields does not match') + values_string = ', '.join(default_value_helper(list(t.reference.fields)[idx], property) for idx, property in enumerate(res)) + return '{0}({{{1}}})'.format(t.type, values_string) + if t.is_model or t.is_list: + if not isinstance(res, list): + jinja_error('default_value: value in annotation is supposed to be a list') + row_string = '' + if t.nested.is_struct and t.is_list: + row_string = ', '.join(('QVariant::fromValue({0})'.format(default_value_helper(t.nested, row))) for row in res) + else: + row_string = ', '.join(default_value_helper(t.nested, row) for row in res) + return '{{{0}}}'.format(row_string) + if t.is_enum or t.is_flag: + module_name = t.reference.module.module_name + return enum_value(res, module_name) + # in case it's bool, Python True is sent to the C++ as "True", let's take care of that + if t.is_bool: + if res: + return 'true' + else: + return 'false' + if t.is_string: + return 'QLatin1String("{0}")'.format(res.replace("\\", "\\\\")) + if t.is_var: + if isinstance(res, str): + res = 'QLatin1String("{0}")'.format(res) + return 'QVariant({0})'.format(res) + + return '{0}'.format(res) + +def default_value(symbol, zone='='): + """ + Find the default value used by the simulator backend + """ + res = default_type_value(symbol) + if symbol.type.is_model: + res = '{}'; + if 'config_simulator' in symbol.tags and 'default' in symbol.tags['config_simulator']: + res = symbol.tags['config_simulator']['default'] + if isinstance(res, dict): + if zone in res: + res = res[zone] + elif '=' in res: + res = res['='] + return default_value_helper(symbol, res) + + return res + +def parameter_type_default(symbol): + """ + Return the parameter declaration for properties, handle camel case module name + """ + prefix = Filters.classPrefix + if symbol.type.is_enum or symbol.type.is_flag: + return '{0}{1}Module::{2} {3}={4}'.format(prefix, upper_first(symbol.type.reference.module.module_name), flag_type(symbol), symbol, default_type_value(symbol)) + if symbol.type.is_void or symbol.type.is_primitive: + if symbol.type.name == 'string': + return 'const QString &{0}=QString()'.format(symbol) + if symbol.type.name == 'var': + return 'const QVariant &{0}=QVariant()'.format(symbol) + if symbol.type.name == 'real': + return 'qreal {0}=qreal()'.format(symbol) + return '{0} {1}={2}'.format(symbol.type, symbol, default_type_value(symbol)) + elif symbol.type.is_list: + nested = return_type(symbol.type.nested) + return 'const QVariantList &{1}=QVariantList()'.format(nested, symbol) + elif symbol.type.is_model: + nested = symbol.type.nested + if nested.is_primitive: + return '{0}VariantModel *{1}=QVariantModel'.format(prefix, symbol) + elif nested.is_complex: + return 'QIviPagingModel *{0}=nullptr'.format(symbol) + else: + return 'const {0}{1} &{2}={0}{1}()'.format(prefix, symbol.type.reference.name, symbol) + jinja_error('parameter_type_default: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) + +def parameter_type(symbol): + """ + Return the parameter declaration for properties, handle camel case module name + """ + prefix = Filters.classPrefix + if symbol.type.is_enum or symbol.type.is_flag: + return '{0}{1}Module::{2} {3}'.format(prefix, upper_first(symbol.type.reference.module.module_name), flag_type(symbol), symbol) + if symbol.type.is_void or symbol.type.is_primitive: + if symbol.type.name == 'string': + return 'const QString &{0}'.format(symbol) + if symbol.type.name == 'var': + return 'const QVariant &{0}'.format(symbol) + if symbol.type.name == 'real': + return 'qreal {0}'.format(symbol) + return '{0} {1}'.format(symbol.type, symbol) + elif symbol.type.is_list: + nested = return_type(symbol.type.nested) + return 'const QVariantList &{1}'.format(nested, symbol) + elif symbol.type.is_model: + nested = symbol.type.nested + if nested.is_primitive: + return '{0}VariantModel *{1}'.format(prefix, symbol) + elif nested.is_complex: + return 'QIviPagingModel *{0}'.format(symbol) + else: + return 'const {0}{1} &{2}'.format(prefix, symbol.type.reference.name, symbol) + jinja_error('parameter_type: Unknown parameter {0} of type {1}'.format(symbol, symbol.type)) + + +def return_type(symbol): + """ + Return the type declaration for properties, handle camel case module name + """ + prefix = Filters.classPrefix + if symbol.type.is_enum or symbol.type.is_flag: + return('{0}{1}Module::{2}'.format(prefix, upper_first(symbol.type.reference.module.module_name), flag_type(symbol))) + if symbol.type.is_void or symbol.type.is_primitive: + if symbol.type.name == 'string': + return 'QString' + if symbol.type.name == 'var': + return 'QVariant' + if symbol.type.name == 'real': + return 'qreal' + return symbol.type.name + elif symbol.type.is_list: + nested = return_type(symbol.type.nested) + return 'QVariantList'.format(nested) + elif symbol.type.is_model: + nested = symbol.type.nested + if nested.is_primitive: + return '{0}VariantModel *'.format(prefix) + elif nested.is_complex: + return 'QIviPagingModel *' + else: + return '{0}{1}'.format(prefix, symbol.type.reference.name) + jinja_error('return_type: Unknown symbol {0} of type {1}'.format(symbol, symbol.type)) + + +def flag_type(symbol): + """ + Return the annotation for the flag type if available, the plural otherwise + """ + actualType = symbol + if symbol.type.reference: + actualType = symbol.type.reference + if actualType.is_flag: + if 'config' in actualType.tags and 'type' in actualType.tags['config']: + return actualType.tags['config']['type'] + return '{0}s'.format(actualType) + return actualType + + +def domain_values(symbol): + """ + Returns domain values for property (if defined by @domain) + """ + if type(symbol) is Property: + if 'config_simulator' in symbol.tags: + if 'domain' in symbol.tags['config_simulator']: + return symbol.tags['config_simulator']['domain'] + return None + +def getter_name(symbol): + """ + Returns the getter name of the property + """ + if type(symbol) is Property: + if 'config' in symbol.tags and 'getter_name' in symbol.tags['config']: + return symbol.tags['config']['getter_name'] + return symbol + +def setter_name(symbol): + """ + Returns the setter name of the property + """ + if type(symbol) is Property: + if 'config' in symbol.tags and 'setter_name' in symbol.tags['config']: + return symbol.tags['config']['setter_name'] + return 'set' + symbol.name[0].upper() + symbol.name[1:] + +def range_value(symbol, index, key): + """ + Returns value for property (if defined by @range index or key) + """ + if type(symbol) is Property and symbol.type.is_int or symbol.type.is_real: + if 'config_simulator' in symbol.tags: + if 'range' in symbol.tags['config_simulator']: + return symbol.tags['config_simulator']['range'][index] + if key in symbol.tags['config_simulator']: + return symbol.tags['config_simulator'][key] + return None + + +def range_high(symbol): + """ + Returns maximum value for property (if defined by @range or @maximum) + """ + return range_value(symbol, 1, 'maximum') + + +def range_low(symbol): + """ + Returns minimum value for property (if defined by @range or @minimum) + """ + return range_value(symbol, 0, 'minimum') + + +def has_domains(properties): + """ + Returns true if any property has range or domain tags + """ + for property in properties: + if 'config_simulator' in property.tags: + for p in ['range', 'domain', 'minimum', 'maximum']: + if p in property.tags['config_simulator']: + return True + return False + +def strip_QT(s): + """ + If the given string starts with QT, stip it away. + """ + s = str(s) + if s.startswith('QT'): + return s[2:] + return s + +def json_domain(properties): + """ + Returns property domains formated in json + """ + data = {} + if len(properties): + data["iviVersion"] = builtin_config.config["VERSION"] + for property in properties: + if 'config_simulator' in property.tags: + for p in ['range', 'domain', 'minimum', 'maximum']: + if property.tags['config_simulator'] is not None and p in property.tags['config_simulator']: + if not property.name in data: + data[property.name] = {} + data[property.name][p] = property.tags['config_simulator'][p] + return json.dumps(data, separators=(',', ':')) + +def simulationData(module): + + found = False + data = {} + for interface in module.interfaces: + iData = {} + if 'config_simulator' in interface.tags: + iData = interface.tags['config_simulator'] + for property in interface.properties: + if 'config_simulator' in property.tags: + for p in ['range', 'domain', 'minimum', 'maximum', 'default']: + if property.tags['config_simulator'] is not None and p in property.tags['config_simulator']: + if not property.name in iData: + iData[property.name] = {} + iData[property.name][p] = symbolToJson(property.tags['config_simulator'][p], property.type) + data[interface.name] = iData + return json.dumps(data, indent=' ') + +def symbolToJson(data, symbol): + if symbol.type.is_struct: + t = symbol.type + if not (isinstance(data, dict) or isinstance(data, list)): + jinja_error('simulationData: value in annotation is supposed to be a dict or list') + if len(data) != len(t.reference.fields): + jinja_error('simulationData: argument count in annotation and number of struct fields does not match') + newList = list(symbolToJson(property, list(t.reference.fields)[idx]) for idx, property in enumerate(data)) + return { "type": symbol.type.name, "value": newList } + elif symbol.type.is_enum or symbol.type.is_flag: + module_name = symbol.type.reference.module.module_name + return { "type": "enum", "value": enum_value(data, module_name) } + elif symbol.type.is_list or symbol.type.is_model: + nested = symbol.type.nested + if nested.is_complex: + newList = [] + for value in data: + newList.append(symbolToJson(value, nested)) + return newList + return data + +def qml_control_properties(symbol, backend_object): + """ + Returns properties of the QML control matching to this + IDL type (e.g. min/max properties) + """ + prop_str = lower_first_filter(symbol) + "Control" + if isinstance(symbol, Property): + top = range_high(symbol) + bottom = range_low(symbol) + binding = "value: {0}.{1};".format(backend_object, symbol.name) + if top is not None and bottom is not None: + return 'id: {0}; from: {1}; to: {2}; {3}'.format(prop_str, bottom, top, binding) + + if top is not None or bottom is not None: + if top is None: + return 'id: {0}; from: {1}; to:100000; {2}'.format(prop_str, bottom, binding) + elif bottom is None: + return 'id: {0}; from:-100000; to: {1}; {2}'.format(prop_str, top, binding) + + values = domain_values(symbol) + if values is None and (symbol.type.is_enum or symbol.type.is_flag): + values_string = ' '.join('ListElement {{ key: "{0}"; value: {1}.{0} }}'.format(e, qml_type(symbol.interface)) for e in symbol.type.reference.members) + return 'id: {0}; textRole: "key"; {2} model: ListModel {{ {1} }}'.format(prop_str, values_string, binding) + if values is not None: + values_string = ','.join('"'+str(e)+'"' for e in values) + return 'id: {0}; model: [ {1} ]; '.format(prop_str, values_string) + if symbol.type.is_bool: + binding = "checked: {0}.{1};".format(backend_object, symbol.name) + return 'id: {0}; {1}'.format(prop_str, binding) + if symbol.type.is_real or symbol.type.is_int or symbol.type.is_string: + binding = "text: {0}.{1};".format(backend_object, symbol.name) + return 'id: {0}; {1}'.format(prop_str, binding) + + if isinstance(symbol, Parameter): + return 'id: {1}Param{0}'.format(prop_str, symbol.operation) + if isinstance(symbol, Field): + return 'id: {1}_{0}'.format(prop_str, lower_first_filter(symbol.struct)) + +def qml_control_signal_parameters(symbol): + """ + Returns the parameters for calling the signal using the values from the ui controls + """ + return ', '.join('{0}Param{1}Control.{2}'.format(e.operation, lower_first_filter(e), qml_binding_property(e)) for e in symbol.parameters) + +def qml_meta_control_name(symbol): + """ + Returns name of the QML control needed to display this type based on the meta + data of the symbol -- if symbol has some meta data (e.g. value limits or domain) + then control name is taken based on these constraints. Otherwise returns None. + """ + top = range_high(symbol) + bottom = range_low(symbol) + if top is not None and bottom is not None: + return 'Slider' + + if top is not None or bottom is not None: + return 'SpinBox' + + values = domain_values(symbol) + if values is not None: + return "ComboBox" + +def qml_type_control_name(symbol): + """ + Returns name of the QML control inferred based on the type of the symbol. + """ + t = symbol.type + if t.is_string or t.is_int or t.is_real: + return "TextField" + elif t.is_bool: + return "CheckBox" + elif t.is_enum: + if t.reference.is_enum: + return "EnumControl" + elif t.reference.is_flag: + return "FlagControl" + elif t.is_flag: + return "FlagControl" + return "TextField" + + +def qml_control_name(symbol): + """ + Returns name of the QML control for the symbol. First it checks meta data (as it may + influence the control type) and if nothing is defined there, it falls back to the + symbol actual type. + """ + # First try to calculate control name based on the tags + control_name = qml_meta_control_name(symbol) + # If nothing is defined, calculate it based on its type + if control_name is None: + control_name = qml_type_control_name(symbol) + return control_name + + +def qml_control(symbol, backend_object): + """ + Returns QML code for the control (or group of controls) to represent the editing UI for the symbol. + """ + + if symbol.type.is_struct: + return qml_struct_control(symbol) + + return "{0} {{ {1} }}".format(qml_control_name(symbol), qml_control_properties(symbol, backend_object)) + + +def qml_binding_property(symbol): + """ + :param symbol: property which is being bound by the control + :return: name of the property of the QML control to be bound with + """ + control_name = qml_control_name(symbol) + if control_name == "CheckBox": + return "checked" + elif control_name == "Slider" or control_name == "SpinBox" or control_name == "FlagControl" or control_name == "EnumControl": + return "value" + elif control_name == "TextField": + return "text" + elif control_name == "ComboBox": + return "currentIndex" + +def qml_struct_control(symbol): + if symbol.type.is_struct and symbol.type.reference.fields: + result = "Rectangle { ColumnLayout { " + for field in symbol.type.reference.fields: + result += qml_control(field) + result += "}}" + return result + + +def qml_info_type(symbol): + """ + Returns the correct type for the symbol, to be used inside the qmltype templates + """ + prefix = Filters.classPrefix + if symbol.type.is_enum or symbol.type.is_flag: + return('{0}{1}Module::{2}'.format(prefix, upper_first(symbol.module.module_name), flag_type(symbol))) + elif symbol.type.is_void or symbol.type.is_primitive: + if symbol.type.is_real: + return 'double' + return symbol.type.name + elif symbol.type.is_struct: + return 'QVariant' + elif symbol.type.is_list: + return 'QVariantList' + elif symbol.type.is_model: + return 'QIviPagingModel' + else: + jinja_error('qml_info_type: Unknown symbol {0} of type {1}'.format(symbol, symbol.type)) + +def qml_type(symbol): + """ + :param interface: + :return: Returns the name of the interface for using in QML. This name is defined in the IDL under + the "config" tag as "qml_type". This annotation is optional, if not provided, the interface name is + used. + """ + result = symbol.name + if 'qml_type' in symbol.tags['config']: + result = symbol.tags['config']['qml_type'] + elif 'qml_name' in symbol.tags['config']: + result = symbol.tags['config']['qml_name'] + return result + +def model_type(symbol): + if symbol.type.is_model: + nested = symbol.type.nested + return '{0}Model'.format(nested) + return None + + +def struct_includes(symbol): + includesSet = set() + tpl = '#include \"{0}.h\"' + + module = symbol.module + + if isinstance(symbol, Struct): + for val in symbol.fields: + if val.type.is_struct: + includesSet.add(tpl.format(val.type.reference.name).lower()) + elif isinstance(symbol, Interface): + for val in symbol.properties: + if val.type.is_struct: + includesSet.add(tpl.format(val.type.reference.name).lower()) + for op in symbol.operations: + for param in op.parameters: + if param.type.is_struct: + includesSet.add(tpl.format(param.type.reference.name).lower()) + if op.type.is_struct: + includesSet.add(tpl.format(op.type.reference.name).lower()) + for op in symbol.signals: + for param in op.parameters: + if param.type.is_struct: + includesSet.add(tpl.format(param.type.reference.name).lower()) + + return includesSet + +def comment_text(comment): + """ + Returns the text of the passed comment without the leading/trailing comment tokens e.g. /**, * + """ + comment_start = [ '/**', '/*!', '/*'] + processed = [] + isComment = False + + # No comment is NOT a error + if len(comment) == 0: + return processed + + for token in comment_start: + if (comment.startswith(token)): + isComment = True + break; + if isComment: + comment = comment[3:-2] + else: + jinja_error("comment_text: The provided comment needs to be start with one of these strings: {}".format(comment_start)) + + for line in comment.splitlines(): + line = line.lstrip(" *") + processed.append(line) + return processed + + +def register_filters(generator): + generator.env.keep_trailing_newline = True + generator.register_filter('return_type', return_type) + generator.register_filter('parameter_type_default', parameter_type_default) + generator.register_filter('parameter_type', parameter_type) + generator.register_filter('getter_name', getter_name) + generator.register_filter('setter_name', setter_name) + generator.register_filter('test_type_value', test_type_value) + generator.register_filter('default_type_value', default_type_value) + generator.register_filter('default_value', default_value) + generator.register_filter('model_type', model_type) + generator.register_filter('flag_type', flag_type) + generator.register_filter('range_low', range_low) + generator.register_filter('range_high', range_high) + generator.register_filter('strip_QT', strip_QT) + generator.register_filter('domain_values', domain_values) + generator.register_filter("enum_value", enum_value) + generator.register_filter("tag_by_path", tag_by_path) + generator.register_filter("conf_sim_tag", conf_sim_tag) + generator.register_filter('has_domains', has_domains) + generator.register_filter('simulationData', simulationData) + generator.register_filter('json_domain', json_domain) + generator.register_filter('qml_info_type', qml_info_type) + generator.register_filter('qml_type', qml_type) + generator.register_filter('qml_control', qml_control) + generator.register_filter('qml_binding_property', qml_binding_property) + generator.register_filter('qml_control_signal_parameters', qml_control_signal_parameters) + generator.register_filter('struct_includes', struct_includes) + generator.register_filter('comment_text', comment_text) diff --git a/src/tools/ivigenerator/generator/global_functions.py b/src/tools/ivigenerator/generator/global_functions.py new file mode 100644 index 0000000..6794fc5 --- /dev/null +++ b/src/tools/ivigenerator/generator/global_functions.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019 The Qt Company Ltd. +# Copyright (C) 2019 Luxoft Sweden AB +# Copyright (C) 2018 Pelagicore AG +# Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB) +# Contact: https://www.qt.io/licensing/ +# +# This file is part of the QtIvi module of the Qt Toolkit. +# +# $QT_BEGIN_LICENSE:LGPL-QTAS$ +# Commercial License Usage +# Licensees holding valid commercial Qt Automotive Suite licenses may use +# this file in accordance with the commercial license agreement provided +# with the Software or, alternatively, in accordance with the terms +# contained in a written agreement between you and The Qt Company. For +# licensing terms and conditions see https://www.qt.io/terms-conditions. +# For further information use the contact form at https://www.qt.io/contact-us. +# +# GNU Lesser General Public License Usage +# Alternatively, this file may be used under the terms of the GNU Lesser +# General Public License version 3 as published by the Free Software +# Foundation and appearing in the file LICENSE.LGPL3 included in the +# packaging of this file. Please review the following information to +# ensure the GNU Lesser General Public License version 3 requirements +# will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 2.0 or (at your option) the GNU General +# Public license version 3 or any later version approved by the KDE Free +# Qt Foundation. The licenses are as published by the Free Software +# Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-2.0.html and +# https://www.gnu.org/licenses/gpl-3.0.html. +# +# $QT_END_LICENSE$ +# +# SPDX-License-Identifier: LGPL-3.0 + +def jinjaTrace(): + """ + Collects all jinja template files and the line numbers from the current calltrace + """ + frame = inspect.currentframe() + infos = [] + while frame: + template = frame.f_globals.get('__jinja_template__') + if template is not None: + infos.append((inspect.getsourcefile(frame), template.get_corresponding_lineno(frame.f_lineno))) + frame = frame.f_back + + return infos + +def jinja_error(msg): + """ + Throws an error for the current jinja template and the templates this is included from + This can be used inside a filter to indicate problems with the passed arguments or direclty inside + an template + """ + infos = jinjaTrace() + if len(infos): + message = msg + if len(infos) > 1: + for info in infos[1:]: + message = message + "\n{0}:{1}: instantiated from here".format(info[0], info[1]) + message = message + "\n{0}: instantiated from here".format(currentQFaceSrcFile) + raise TemplateAssertionError(message, infos[0][1], "", infos[0][0]) + raise TemplateAssertionError(msg, -1, "", "unknown") + +def jinja_warning(msg): + """ + Reports an warning in the current jinja template. + This can be used inside a filter to indicate problems with the passed arguments or direclty inside + an template + """ + file, lineno = jinjaTrace()[0] + if file: + message = '{0}:{1}: warning: {2}'.format(file, lineno, msg) + else: + message = '<unknown-file>: warning: {0}'.format(msg) + click.secho(message, fg='yellow', err=True) + +def register_global_functions(generator): + generator.env.globals["error"] = jinja_error + generator.env.globals["warning"] = jinja_warning diff --git a/src/tools/ivigenerator/ivigenerator.pro b/src/tools/ivigenerator/ivigenerator.pro index d9dc54d..00a71a1 100644 --- a/src/tools/ivigenerator/ivigenerator.pro +++ b/src/tools/ivigenerator/ivigenerator.pro @@ -93,14 +93,19 @@ templates_test.files += \ templates_test/pagingmodel.h.tpl templates_test.path = $$[QT_HOST_BINS]/ivigenerator/templates_test +generator_module.files += \ + generator/global_functions.py \ + generator/builtin_config.py \ + generator/filters.py +generator_module.path = $$[QT_HOST_BINS]/ivigenerator/generator + generator.files += \ generate.py \ $$OUT_PWD/.config \ templates_frontend.yaml \ templates_qmlplugin.yaml \ templates_backend_simulator.yaml \ - templates_test.yaml \ - + templates_test.yaml generator.path = $$[QT_HOST_BINS]/ivigenerator qtConfig(remoteobjects) { @@ -115,6 +120,7 @@ qtConfig(remoteobjects) { } INSTALLS += generator \ + generator_module \ common \ templates_frontend \ templates_qmlplugin \ |