diff options
author | Kostiantyn Sologubov <ksologubov@luxoft.com> | 2020-04-08 18:28:28 +0300 |
---|---|---|
committer | Kostiantyn Sologubov <ksologubov@luxoft.com> | 2020-04-08 19:54:25 +0300 |
commit | e3c4b4f98135760ebe934b2e92af9a6a656fb83b (patch) | |
tree | 005ccbcefc88d050dd9e7dba90ba99e4b471ea26 /generator/transformers | |
parent | 18b4c49d14cf6086e258327d845cc4db0ec67832 (diff) | |
download | sdl_android-e3c4b4f98135760ebe934b2e92af9a6a656fb83b.tar.gz |
moving generators to repository root
Diffstat (limited to 'generator/transformers')
-rw-r--r-- | generator/transformers/__init__.py | 0 | ||||
-rw-r--r-- | generator/transformers/common_producer.py | 120 | ||||
-rw-r--r-- | generator/transformers/enums_producer.py | 87 | ||||
-rw-r--r-- | generator/transformers/functions_producer.py | 149 | ||||
-rw-r--r-- | generator/transformers/generate_error.py | 12 | ||||
-rw-r--r-- | generator/transformers/structs_producer.py | 129 |
6 files changed, 497 insertions, 0 deletions
diff --git a/generator/transformers/__init__.py b/generator/transformers/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/generator/transformers/__init__.py diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py new file mode 100644 index 000000000..eaac41a6b --- /dev/null +++ b/generator/transformers/common_producer.py @@ -0,0 +1,120 @@ +""" +Common transformation +""" + +import logging +import re +from abc import ABC +from collections import namedtuple + +from model.array import Array +from model.enum import Enum +from model.struct import Struct + + +class InterfaceProducerCommon(ABC): + """ + Common transformation + """ + + version = '1.0.0' + + def __init__(self, container_name, enums_package, structs_package, package_name, + enum_names=(), struct_names=(), key_words=()): + self.logger = logging.getLogger('Generator.InterfaceProducerCommon') + self.container_name = container_name + self.enum_names = enum_names + self.struct_names = struct_names + self.key_words = key_words + self.enums_package = enums_package + self.structs_package = structs_package + self.package_name = package_name + self._params = namedtuple('params', 'deprecated description key last mandatory origin return_type since title ' + 'param_doc name') + + @property + def get_version(self): + return self.version + + @property + def params(self): + """ + :return: namedtuple params(name='', origin='') + """ + return self._params + + @staticmethod + def key(param: str): + """ + Convert param string to uppercase and inserting underscores + :param param: camel case string + :return: string in uppercase with underscores + """ + if re.match(r'^[A-Z_\d]+$', param): + return param + else: + return re.sub(r'([a-z]|[A-Z]{2,})([A-Z]|\d$)', r'\1_\2', param).upper() + + @staticmethod + def ending_cutter(n: str): + """ + If string not contains only uppercase letters and end with 'ID' deleting 'ID' from end of string + :param n: string to evaluate and deleting 'ID' from end of string + :return: if match cut string else original string + """ + if re.match(r'^\w+[a-z]+([A-Z]{2,})?ID$', n): + return n[:-2] + else: + return n + + @staticmethod + def extract_description(d): + """ + Evaluate, align and delete @TODO + :param d: list with description + :return: evaluated string + """ + return re.sub(r'(\s{2,}|\n|\[@TODO.+)', ' ', ''.join(d)).strip() if d else '' + + @staticmethod + def replace_sync(name): + """ + :param name: string with item name + :return: string with replaced 'sync' to 'Sdl' + """ + if name: + return re.sub(r'^([sS])ync(.+)$', r'\1dl\2', name) + return name + + def replace_keywords(self, name: str = '') -> str: + """ + if :param name in self.key_words, :return: name += 'Param' + :param name: string with item name + """ + if any(map(lambda k: re.search(r'^(get|set|key_)?{}$'.format(name.casefold()), k), self.key_words)): + origin = name + if name.isupper(): + name += '_PARAM' + else: + name += 'Param' + self.logger.debug('Replacing %s with %s', origin, name) + return self.replace_sync(name) + + def extract_type(self, param): + """ + Evaluate and extract type + :param param: sub-element Param of element from initial Model + :return: string with sub-element type + """ + + def evaluate(t1): + if isinstance(t1, Struct) or isinstance(t1, Enum): + name = t1.name + return name + else: + return type(t1).__name__ + + if isinstance(param.param_type, Array): + return 'List<{}>'.format(evaluate(param.param_type.element_type)) + else: + return evaluate(param.param_type) diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py new file mode 100644 index 000000000..a04046441 --- /dev/null +++ b/generator/transformers/enums_producer.py @@ -0,0 +1,87 @@ +""" +Enums transformation +""" + +import logging +import textwrap +from collections import namedtuple, OrderedDict + +from model.enum import Enum +from model.enum_element import EnumElement +from transformers.common_producer import InterfaceProducerCommon + + +class EnumsProducer(InterfaceProducerCommon): + """ + Enums transformation + """ + + def __init__(self, paths, key_words): + super(EnumsProducer, self).__init__( + container_name='elements', + enums_package=None, + structs_package=None, + package_name=paths.enums_package, + key_words=key_words) + self.logger = logging.getLogger('EnumsProducer') + self._params = namedtuple('params', 'origin name internal description since value deprecated') + + @staticmethod + def converted(name): + if name[0].isdigit(): + name = '_' + name + if '-' in name: + name = name.replace('-', '_') + return name + + def transform(self, item: Enum) -> dict: + """ + Override + :param item: particular element from initial Model + :return: dictionary to be applied to jinja2 template + """ + imports = set() + params = OrderedDict() + if any(map(lambda l: l.name != self.converted(l.name), getattr(item, self.container_name).values())): + kind = 'custom' + imports.add('java.util.EnumSet') + else: + kind = 'simple' + + for param in getattr(item, self.container_name).values(): + p = self.extract_param(param, kind) + params[p.name] = p + + render = OrderedDict() + render['kind'] = kind + render['package_name'] = self.package_name + render['class_name'] = item.name[:1].upper() + item.name[1:] + render['params'] = params + render['since'] = item.since + render['deprecated'] = item.deprecated + + description = self.extract_description(item.description) + if description: + render['description'] = description + if imports: + render['imports'] = imports + + render['params'] = tuple(render['params'].values()) + if 'description' in render and isinstance(render['description'], str): + render['description'] = textwrap.wrap(render['description'], 90) + + return render + + def extract_param(self, param: EnumElement, kind): + d = {'origin': param.name, 'name': self.key(self.converted(param.name))} + if kind == 'custom': + d['internal'] = '"{}"'.format(param.name) + + if getattr(param, 'since', None): + d['since'] = param.since + if getattr(param, 'deprecated', None): + d['deprecated'] = param.deprecated + if getattr(param, 'description', None): + d['description'] = textwrap.wrap(self.extract_description(param.description), 90) + Params = namedtuple('Params', sorted(d)) + return Params(**d) diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py new file mode 100644 index 000000000..5f692a4d0 --- /dev/null +++ b/generator/transformers/functions_producer.py @@ -0,0 +1,149 @@ +""" +Functions transformation +""" + +import logging +import textwrap +from collections import namedtuple, OrderedDict + +from model.function import Function +from model.param import Param +from transformers.common_producer import InterfaceProducerCommon + + +class FunctionsProducer(InterfaceProducerCommon): + """ + Functions transformation + """ + + def __init__(self, paths, enum_names, struct_names, key_words): + super(FunctionsProducer, self).__init__( + container_name='params', + enums_package=paths.enums_package, + structs_package=paths.structs_package, + enum_names=enum_names, + struct_names=struct_names, + package_name=paths.functions_package, + key_words=key_words) + self.logger = logging.getLogger('FunctionsProducer') + self.request_class = paths.request_class + self.response_class = paths.response_class + self.notification_class = paths.notification_class + self._params = namedtuple('Params', self._params._fields + ('SuppressWarnings',)) + + def transform(self, item: Function) -> dict: + """ + Override + :param item: particular element from initial Model + :return: dictionary to be applied to jinja2 template + """ + class_name = self.replace_keywords(item.name[:1].upper() + item.name[1:]) + + imports = {'java.util.Hashtable', 'com.smartdevicelink.protocol.enums.FunctionID'} + extends_class = None + if item.message_type.name == 'response': + extends_class = self.response_class + if not class_name.endswith("Response"): + class_name += 'Response' + imports.add('com.smartdevicelink.proxy.rpc.enums.Result') + imports.add('android.support.annotation.NonNull') + elif item.message_type.name == 'request': + extends_class = self.request_class + elif item.message_type.name == 'notification': + extends_class = self.notification_class + if extends_class: + imports.add(extends_class) + extends_class = extends_class.rpartition('.')[-1] + + params = OrderedDict() + + for param in getattr(item, self.container_name).values(): + param.origin = param.name + param.name = self.replace_keywords(param.name) + i, p = self.extract_param(param) + imports.update(i) + params.update({param.name: p}) + + render = OrderedDict() + render['kind'] = item.message_type.name + render['package_name'] = self.package_name + render['imports'] = self.sort_imports(imports) + render['function_id'] = self.key(self.replace_keywords(item.name)) + render['class_name'] = class_name + render['extends_class'] = extends_class + render['since'] = item.since + render['deprecated'] = item.deprecated + + description = self.extract_description(item.description) + if description: + render['description'] = description + + if params: + render['params'] = tuple(params.values()) + if 'description' in render and isinstance(render['description'], str): + render['description'] = textwrap.wrap(render['description'], 90) + + return render + + def sort_imports(self, imports: set): + sorted_imports = [] + if 'android.support.annotation.NonNull' in imports: + sorted_imports.append('android.support.annotation.NonNull') + imports.remove('android.support.annotation.NonNull') + sorted_imports.append('') + sorted_imports.append('com.smartdevicelink.protocol.enums.FunctionID') + imports.remove('com.smartdevicelink.protocol.enums.FunctionID') + tmp = [] + for i in imports: + if i.rpartition('.')[0] != self.package_name and not i.startswith('java'): + tmp.append(i) + if tmp: + tmp.sort() + sorted_imports.extend(tmp) + if 'java.util.Hashtable' in imports or 'java.util.List' in imports: + sorted_imports.append('') + for i in ('java.util.Hashtable', 'java.util.List', 'java.util.ArrayList', 'java.util.Collections', + 'java.util.zip.CRC32'): + if i in imports: + sorted_imports.append(i) + return sorted_imports + + def extract_param(self, param: Param): + imports = set() + p = OrderedDict() + p['title'] = param.name[:1].upper() + param.name[1:] + p['key'] = 'KEY_' + self.key(param.name) + p['mandatory'] = param.is_mandatory + p['last'] = param.name + if param.since: + p['since'] = param.since + p['deprecated'] = param.deprecated + p['origin'] = param.origin + d = self.extract_description(param.description) + if param.name == 'success': + d = 'whether the request is successfully processed' + if param.name == 'resultCode': + d = 'additional information about a response returning a failed outcome' + if d: + p['description'] = textwrap.wrap(d, 90) + t = self.extract_type(param) + if t == 'EnumSubset' and param.name == 'resultCode': + t = 'Result' + tr = t + if t.startswith('List'): + imports.add('java.util.List') + p['SuppressWarnings'] = 'unchecked' + tr = t.replace('List<', '').rstrip('>') + if t.startswith('Float'): + imports.add('com.smartdevicelink.util.SdlDataTypeConverter') + p['return_type'] = self.replace_keywords(t) + + if tr in self.enum_names: + imports.add('{}.{}'.format(self.enums_package, tr)) + if tr in self.struct_names: + imports.add('{}.{}'.format(self.structs_package, tr)) + if param.is_mandatory: + imports.add('android.support.annotation.NonNull') + + Params = namedtuple('Params', sorted(p)) + return imports, Params(**p) diff --git a/generator/transformers/generate_error.py b/generator/transformers/generate_error.py new file mode 100644 index 000000000..3fe1a75ed --- /dev/null +++ b/generator/transformers/generate_error.py @@ -0,0 +1,12 @@ +""" +Generate error. +""" + + +class GenerateError(Exception): + """Generate error. + + This exception is raised when generator is unable to create + output from given model. + + """ diff --git a/generator/transformers/structs_producer.py b/generator/transformers/structs_producer.py new file mode 100644 index 000000000..ea9b5d5f5 --- /dev/null +++ b/generator/transformers/structs_producer.py @@ -0,0 +1,129 @@ +""" +Structs transformation +""" + +import logging +import textwrap +from collections import namedtuple, OrderedDict + +from model.param import Param +from model.struct import Struct +from transformers.common_producer import InterfaceProducerCommon + + +class StructsProducer(InterfaceProducerCommon): + """ + Structs transformation + """ + + def __init__(self, paths, enum_names, struct_names, key_words): + super(StructsProducer, self).__init__( + container_name='members', + enums_package=paths.enums_package, + structs_package=paths.structs_package, + enum_names=enum_names, + struct_names=struct_names, + package_name=paths.structs_package, + key_words=key_words) + self.logger = logging.getLogger('StructsProducer') + self.struct_class = paths.struct_class + + def transform(self, item: Struct) -> dict: + """ + Override + :param item: particular element from initial Model + :return: dictionary to be applied to jinja2 template + """ + class_name = self.replace_keywords(item.name[:1].upper() + item.name[1:]) + + imports = {'java.util.Hashtable'} + extends_class = self.struct_class + + imports.add(extends_class) + extends_class = extends_class.rpartition('.')[-1] + + params = OrderedDict() + + for param in getattr(item, self.container_name).values(): + param.name = self.replace_keywords(param.name) + i, p = self.extract_param(param) + imports.update(i) + params[param.name] = p + + render = OrderedDict() + render['class_name'] = class_name + render['extends_class'] = extends_class + render['package_name'] = self.package_name + render['imports'] = imports + render['deprecated'] = item.deprecated + render['since'] = item.since + + description = self.extract_description(item.description) + if description: + render['description'] = description + if params: + render['params'] = params + + if 'imports' in render: + render['imports'] = self.sort_imports(render['imports']) + if params: + render['params'] = tuple(render['params'].values()) + if 'description' in render and isinstance(render['description'], str): + render['description'] = textwrap.wrap(render['description'], 90) + + return render + + def sort_imports(self, imports: set): + sorted_imports = [] + if 'android.support.annotation.NonNull' in imports: + sorted_imports.append('android.support.annotation.NonNull') + imports.remove('android.support.annotation.NonNull') + sorted_imports.append('') + tmp = [] + for i in imports: + if i.rpartition('.')[0] != self.package_name and not i.startswith('java'): + tmp.append(i) + if tmp: + tmp.sort() + sorted_imports.extend(tmp) + if 'java.util.Hashtable' in imports or 'java.util.List' in imports: + sorted_imports.append('') + for i in ('java.util.Hashtable', 'java.util.List', 'java.util.ArrayList', 'java.util.Collections'): + if i in imports: + sorted_imports.append(i) + return sorted_imports + + def extract_param(self, param: Param): + imports = set() + p = OrderedDict() + p['title'] = param.name[:1].upper() + param.name[1:] + p['key'] = 'KEY_' + self.key(param.name) + p['mandatory'] = param.is_mandatory + p['last'] = param.name + if param.since: + p['since'] = param.since + p['deprecated'] = param.deprecated + p['origin'] = param.name + + d = self.extract_description(param.description) + if d: + p['description'] = textwrap.wrap(d, 90) + t = self.extract_type(param) + tr = t + if t.startswith('List'): + imports.add('java.util.List') + p['SuppressWarnings'] = 'unchecked' + tr = t.replace('List<', '').rstrip('>') + if t.startswith('Float'): + imports.add('com.smartdevicelink.util.SdlDataTypeConverter') + p['return_type'] = self.replace_sync(t) + + if tr in self.enum_names: + imports.add('{}.{}'.format(self.enums_package, tr)) + if tr in self.struct_names: + imports.add('{}.{}'.format(self.structs_package, tr)) + if param.is_mandatory: + imports.add('android.support.annotation.NonNull') + + Params = namedtuple('Params', sorted(p)) + return imports, Params(**p) |