summaryrefslogtreecommitdiff
path: root/generator/transformers
diff options
context:
space:
mode:
authorKostiantyn Sologubov <ksologubov@luxoft.com>2020-04-08 18:28:28 +0300
committerKostiantyn Sologubov <ksologubov@luxoft.com>2020-04-08 19:54:25 +0300
commite3c4b4f98135760ebe934b2e92af9a6a656fb83b (patch)
tree005ccbcefc88d050dd9e7dba90ba99e4b471ea26 /generator/transformers
parent18b4c49d14cf6086e258327d845cc4db0ec67832 (diff)
downloadsdl_android-e3c4b4f98135760ebe934b2e92af9a6a656fb83b.tar.gz
moving generators to repository root
Diffstat (limited to 'generator/transformers')
-rw-r--r--generator/transformers/__init__.py0
-rw-r--r--generator/transformers/common_producer.py120
-rw-r--r--generator/transformers/enums_producer.py87
-rw-r--r--generator/transformers/functions_producer.py149
-rw-r--r--generator/transformers/generate_error.py12
-rw-r--r--generator/transformers/structs_producer.py129
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)