From ce593107c5fda551012c5e5a4698c1ceaef7034c Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Wed, 15 Jan 2020 19:37:22 +0200 Subject: [PATCH 01/28] SDL-0234 Proxy Library RPC Generation --- .gitignore | 7 + .gitmodules | 4 + generator/README.md | 47 ++ generator/__init__.py | 0 generator/generator.py | 479 +++++++++++++++++ generator/mapping.json | 11 + generator/paths.ini | 7 + generator/requirements.txt | 5 + generator/rpc_spec | 1 + generator/templates/SDLRPCFunctionNames.h | 26 + generator/templates/SDLRPCFunctionNames.m | 7 + generator/templates/SDLRPCParameterNames.h | 14 + generator/templates/SDLRPCParameterNames.m | 12 + generator/templates/base_struct_function.h | 93 ++++ generator/templates/base_struct_function.m | 65 +++ generator/templates/copyright.txt | 31 ++ generator/templates/enums/FunctionID.h | 29 + generator/templates/enums/FunctionID.m | 52 ++ generator/templates/enums/template.h | 52 ++ generator/templates/enums/template.m | 12 + generator/templates/functions/template.h | 1 + generator/templates/functions/template.m | 18 + generator/templates/structs/template.h | 1 + generator/templates/structs/template.m | 6 + generator/test/__init__.py | 0 generator/test/runner.py | 54 ++ generator/test/test_CodeFormatAndQuality.py | 37 ++ generator/test/test_enums.py | 56 ++ generator/test/test_functions.py | 351 ++++++++++++ generator/test/test_structs.py | 72 +++ generator/transformers/__init__.py | 0 generator/transformers/common_producer.py | 536 +++++++++++++++++++ generator/transformers/enums_producer.py | 71 +++ generator/transformers/functions_producer.py | 139 +++++ generator/transformers/generate_error.py | 12 + generator/transformers/structs_producer.py | 44 ++ 36 files changed, 2352 insertions(+) create mode 100644 generator/README.md create mode 100644 generator/__init__.py create mode 100644 generator/generator.py create mode 100644 generator/mapping.json create mode 100644 generator/paths.ini create mode 100644 generator/requirements.txt create mode 160000 generator/rpc_spec create mode 100644 generator/templates/SDLRPCFunctionNames.h create mode 100644 generator/templates/SDLRPCFunctionNames.m create mode 100644 generator/templates/SDLRPCParameterNames.h create mode 100644 generator/templates/SDLRPCParameterNames.m create mode 100644 generator/templates/base_struct_function.h create mode 100644 generator/templates/base_struct_function.m create mode 100644 generator/templates/copyright.txt create mode 100644 generator/templates/enums/FunctionID.h create mode 100644 generator/templates/enums/FunctionID.m create mode 100644 generator/templates/enums/template.h create mode 100644 generator/templates/enums/template.m create mode 100644 generator/templates/functions/template.h create mode 100644 generator/templates/functions/template.m create mode 100644 generator/templates/structs/template.h create mode 100644 generator/templates/structs/template.m create mode 100644 generator/test/__init__.py create mode 100644 generator/test/runner.py create mode 100755 generator/test/test_CodeFormatAndQuality.py create mode 100644 generator/test/test_enums.py create mode 100644 generator/test/test_functions.py create mode 100644 generator/test/test_structs.py create mode 100644 generator/transformers/__init__.py create mode 100644 generator/transformers/common_producer.py create mode 100644 generator/transformers/enums_producer.py create mode 100644 generator/transformers/functions_producer.py create mode 100644 generator/transformers/generate_error.py create mode 100644 generator/transformers/structs_producer.py diff --git a/.gitignore b/.gitignore index 58a919ead..cd964170f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,10 @@ infer-out Carthage/Build docs/docsets/ +.idea +*venv* +*__pycache__ +*.pytest_cache +**htmlcov +**.coverage +_debug* diff --git a/.gitmodules b/.gitmodules index 684da2889..6336d3034 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "bson_c_lib"] path = bson_c_lib url = https://github.com/smartdevicelink/bson_c_lib.git +[submodule "generator/rpc_spec"] + path = generator/rpc_spec + url = https://github.com/smartdevicelink/rpc_spec.git + branch = develop diff --git a/generator/README.md b/generator/README.md new file mode 100644 index 000000000..623f46d94 --- /dev/null +++ b/generator/README.md @@ -0,0 +1,47 @@ +# Proxy Library RPC Generator + +## Overview + +This script provides the possibility to auto-generate iOS code based on a given SDL MOBILE_API XML specification. + +## Requirements + +The script requires Python 3.5 pre-installed in the system. This is the minimal Python 3 version that has not reached the end-of-life (https://devguide.python.org/devcycle/#end-of-life-branches). + +Some required libraries are described in `requirements.txt` and should be pre-installed by the command: +```shell script +pip install -r requirements.txt +``` +Please also make sure before usage the 'generator/rpc_spec' Git submodule is successfully initialized, because the script uses the XML parser provided there. + +## Usage +```shell script +usage: runner.py [-h] [-v] [-xml SOURCE_XML] [-xsd SOURCE_XSD] + [-d OUTPUT_DIRECTORY] [-t [TEMPLATES_DIRECTORY]] + [-r REGEX_PATTERN] [--verbose] [-e] [-s] [-m] [-y] [-n] + +Proxy Library RPC Generator + +optional arguments: + -h, --help show this help message and exit + -v, --version print the version and exit + -xml SOURCE_XML, --source-xml SOURCE_XML, --input-file SOURCE_XML + should point to MOBILE_API.xml + -xsd SOURCE_XSD, --source-xsd SOURCE_XSD + -d OUTPUT_DIRECTORY, --output-directory OUTPUT_DIRECTORY + define the place where the generated output should be + placed + -t [TEMPLATES_DIRECTORY], --templates-directory [TEMPLATES_DIRECTORY] + path to directory with templates + -r REGEX_PATTERN, --regex-pattern REGEX_PATTERN + only elements matched with defined regex pattern will + be parsed and generated + --verbose display additional details like logs etc + -e, --enums only specified elements will be generated, if present + -s, --structs only specified elements will be generated, if present + -m, -f, --functions only specified elements will be generated, if present + -y, --overwrite force overwriting of existing files in output + directory, ignore confirmation message + -n, --skip skip overwriting of existing files in output + directory, ignore confirmation message +``` diff --git a/generator/__init__.py b/generator/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/generator/generator.py b/generator/generator.py new file mode 100644 index 000000000..1b2a06aa4 --- /dev/null +++ b/generator/generator.py @@ -0,0 +1,479 @@ +""" +Generator +""" +import asyncio +import json +import logging +import re +import sys +from argparse import ArgumentParser +from collections import namedtuple, OrderedDict +from datetime import datetime, date +from inspect import getfile +from json import JSONDecodeError +from os.path import basename, join +from re import findall + +from jinja2 import UndefinedError, TemplateNotFound, FileSystemLoader, Environment, ChoiceLoader, \ + TemplateAssertionError, TemplateSyntaxError, TemplateRuntimeError +from pathlib2 import Path + +ROOT = Path(__file__).absolute().parents[0] + +sys.path.append(ROOT.joinpath('rpc_spec/InterfaceParser').as_posix()) + +try: + from parsers.rpc_base import ParseError + from parsers.sdl_rpc_v2 import Parser + from model.interface import Interface + from transformers.common_producer import InterfaceProducerCommon as Common + from transformers.enums_producer import EnumsProducer + from transformers.functions_producer import FunctionsProducer + from transformers.structs_producer import StructsProducer +except ImportError as error: + print('%s.\nprobably you did not initialize submodule', error) + ParseError = Parser = Interface = Common = EnumsProducer = FunctionsProducer = StructsProducer = None + sys.exit(1) + + +class Generator: + """ + This class contains only technical features, as follow: + - parsing command-line arguments, or evaluating required container interactively; + - calling parsers to get Model from xml; + - calling producers to transform initial Model to dict used in jinja2 templates + Not required to be covered by unit tests cause contains only technical features. + """ + + def __init__(self): + self.logger = logging.getLogger(self.__class__.__name__) + self._env = None + self._output_directory = None + self.loop = asyncio.get_event_loop() + self.paths_named = namedtuple('paths_named', 'enum_class struct_class request_class response_class ' + 'notification_class function_names parameter_names') + + @property + def output_directory(self) -> Path: + """ + + :return: + """ + return self._output_directory + + @output_directory.setter + def output_directory(self, output_directory): + """ + + :param output_directory: + :return: + """ + if output_directory.startswith('/'): + path = Path(output_directory).absolute().resolve() + else: + path = ROOT.joinpath(output_directory).resolve() + if not path.exists(): + self.logger.warning('Directory not found: %s, trying to create it', path) + try: + path.mkdir(parents=True, exist_ok=True) + except OSError as message1: + self.logger.critical('Failed to create directory %s, %s', path.as_posix(), message1) + sys.exit(1) + self._output_directory = path + + @property + def env(self) -> Environment: + """ + + :return: + """ + return self._env + + @env.setter + def env(self, paths): + """ + + :param paths: + :return: + """ + loaders = list(filter(lambda l: Path(l).exists(), paths)) + if not loaders: + self.logger.error('Directory with templates not found %s', str(paths)) + sys.exit(1) + loaders = [FileSystemLoader(l) for l in loaders] + + self._env = Environment(loader=ChoiceLoader(loaders)) + self._env.filters['title'] = self.title + self._env.globals['year'] = date.today().year + + @staticmethod + def title(name): + """ + + :param name: + :return: + """ + return name[:1].upper() + name[1:] + + @property + def get_version(self): + """ + + :return: + """ + return Common.version + + def config_logging(self, verbose): + """ + + :param verbose: + :return: + """ + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(fmt='%(asctime)s.%(msecs)03d - %(levelname)s - %(message)s', + datefmt='%H:%M:%S')) + root_logger = logging.getLogger() + + if verbose: + handler.setLevel(logging.DEBUG) + self.logger.setLevel(logging.DEBUG) + root_logger.setLevel(logging.DEBUG) + else: + handler.setLevel(logging.ERROR) + self.logger.setLevel(logging.ERROR) + root_logger.setLevel(logging.ERROR) + logging.getLogger().handlers.clear() + root_logger.addHandler(handler) + + def get_parser(self): + """ + Parsing command-line arguments, or evaluating required container interactively. + :return: an instance of argparse.ArgumentParser + """ + if len(sys.argv) == 2 and sys.argv[1] in ('-v', '--version'): + print(self.get_version) + sys.exit(0) + + container = namedtuple('container', 'name path') + xml = container('source_xml', ROOT.joinpath('rpc_spec/MOBILE_API.xml')) + required_source = not xml.path.exists() + + out = container('output_directory', ROOT.parents[0].joinpath('SmartDeviceLink')) + output_required = not out.path.exists() + + parser = ArgumentParser(description='Proxy Library RPC Generator') + parser.add_argument('-v', '--version', action='store_true', help='print the version and sys.exit') + parser.add_argument('-xml', '--source-xml', '--input-file', required=required_source, + help='should point to MOBILE_API.xml') + parser.add_argument('-xsd', '--source-xsd', required=False) + parser.add_argument('-d', '--output-directory', required=output_required, + help='define the place where the generated output should be placed') + parser.add_argument('-t', '--templates-directory', nargs='?', default=ROOT.joinpath('templates').as_posix(), + help='path to directory with templates') + parser.add_argument('-r', '--regex-pattern', required=False, + help='only elements matched with defined regex pattern will be parsed and generated') + parser.add_argument('--verbose', action='store_true', help='display additional details like logs etc') + parser.add_argument('-e', '--enums', required=False, action='store_true', + help='only specified elements will be generated, if present') + parser.add_argument('-s', '--structs', required=False, action='store_true', + help='only specified elements will be generated, if present') + parser.add_argument('-m', '-f', '--functions', required=False, action='store_true', + help='only specified elements will be generated, if present') + parser.add_argument('-y', '--overwrite', action='store_true', + help='force overwriting of existing files in output directory, ignore confirmation message') + parser.add_argument('-n', '--skip', action='store_true', + help='skip overwriting of existing files in output directory, ignore confirmation message') + + args, unknown = parser.parse_known_args() + + if unknown: + print('found unknown arguments: ' + ' '.join(unknown)) + parser.print_help(sys.stderr) + sys.exit(1) + + if args.skip and args.overwrite: + print('please select only one option skip "-n" or overwrite "-y"') + sys.exit(1) + + if not args.enums and not args.structs and not args.functions: + args.enums = args.structs = args.functions = True + + for kind in (xml, out): + if not getattr(args, kind.name) and kind.path.exists(): + while True: + try: + confirm = input('Confirm default path {} for {} Y/Enter = yes, N = no' + .format(kind.path, kind.name)) + if confirm.lower() == 'y' or not confirm: + print('{} set to {}'.format(kind.name, kind.path)) + setattr(args, kind.name, kind.path.as_posix()) + break + if confirm.lower() == 'n': + print('provide argument ' + kind.name) + sys.exit(1) + except KeyboardInterrupt: + print('\nThe user interrupted the execution of the program') + sys.exit(1) + + self.logger.debug('parsed arguments:\n%s', vars(args)) + + return args + + def versions_compatibility_validating(self): + """version of generator script requires the same or lesser version of parser script. + if the parser script needs to fix a bug (and becomes, e.g. 1.0.1) and the generator script stays at 1.0.0. + As long as the generator script is the same or greater major version, it should be parsable. + This requires some level of backward compatibility. E.g. they have to be the same major version. + + """ + + regex = r'(\d+\.\d+).(\d)' + + parser_origin = Parser().get_version + generator_origin = self.get_version + parser_split = findall(regex, parser_origin).pop() + generator_split = findall(regex, generator_origin).pop() + + parser_major = float(parser_split[0]) + generator_major = float(generator_split[0]) + + if parser_major > generator_major: + self.logger.critical('Generator (%s) requires the same or lesser version of Parser (%s)', + generator_origin, parser_origin) + sys.exit(1) + + self.logger.info('Parser type: %s, version %s,\tGenerator version %s', + basename(getfile(Parser().__class__)), parser_origin, generator_origin) + + async def get_paths(self, file_name=ROOT.joinpath('paths.ini')): + """ + :param file_name: path to file with container + :return: namedtuple with container to key elements + """ + data = OrderedDict() + try: + with file_name.open('r') as file: + for line in file: + if line.startswith('#'): + self.logger.warning('commented property %s, which will be skipped', line.strip()) + continue + if re.match(r'^(\w+)\s?=\s?(.+)', line): + if len(line.split('=')) > 2: + self.logger.critical('can not evaluate value, too many separators %s', str(line)) + sys.exit(1) + name, var = line.partition('=')[::2] + if name.strip() in data: + self.logger.critical('duplicate key %s', name) + sys.exit(1) + data[name.strip().lower()] = var.strip() + except FileNotFoundError as message1: + self.logger.critical(message1) + sys.exit(1) + + missed = list(set(self.paths_named._fields) - set(data.keys())) + if missed: + self.logger.critical('in %s missed fields: %s ', file, str(missed)) + sys.exit(1) + + return self.paths_named(**data) + + async def get_mappings(self, file=ROOT.joinpath('mapping.json')): + """ + The key name in *.json is equal to property named in jinja2 templates + :param file: path to file with manual mappings + :return: dictionary with custom manual mappings + """ + try: + with file.open('r') as handler: + content = handler.readlines() + return json.loads(''.join(content)) + except (FileNotFoundError, JSONDecodeError) as error1: + self.logger.error('Failure to get mappings %s', error1) + return OrderedDict() + + def write_file(self, file, templates, data): + """ + Calling producer/transformer instance to transform initial Model to dict used in jinja2 templates. + Applying transformed dict to jinja2 templates and writing to appropriate file + :param file: output js file + :param templates: name of template + :param data: an instance of transformer for particular item + """ + try: + render = self.env.get_or_select_template(templates).render(data) + with file.open('w', encoding='utf-8') as handler: + handler.write(render) + except (TemplateNotFound, UndefinedError, TemplateAssertionError, TemplateSyntaxError, TemplateRuntimeError) \ + as error1: + self.logger.error('skipping %s, template not found %s', file.as_posix(), error1) + + async def process_main(self, skip, overwrite, items, transformer): + """ + Process each item from initial Model. According to provided arguments skipping, overriding or asking what to to. + :param skip: if file exist skip it + :param overwrite: if file exist overwrite it + :param items: elements initial Model + :param transformer: producer/transformer instance + """ + tasks = [] + for item in items.values(): + render = transformer.transform(item) + file = self.output_directory.joinpath(render.get('name', item.name)) + for extension in ('.h', '.m'): + data = render.copy() + data['imports'] = data['imports'][extension] + file_with_suffix = file.with_suffix(extension) + templates = ['{}s/template{}'.format(type(item).__name__.lower(), extension)] + if 'template' in data: + templates.insert(0, data['template'] + extension) + tasks.append(self.process_common(skip, overwrite, file_with_suffix, data, templates)) + + await asyncio.gather(*tasks) + + async def process_function_name(self, skip, overwrite, functions, structs, transformer): + """ + + :param skip: + :param overwrite: + :param functions: + :param structs: + :param transformer: + :return: + """ + tasks = [] + for name in [transformer.function_names, transformer.parameter_names]: + file = self.output_directory.joinpath(name) + if name == transformer.function_names: + data = transformer.get_function_names(functions) + elif name == transformer.parameter_names: + data = transformer.get_simple_params(functions, structs) + else: + self.logger.error('No "data" for %s', name) + continue + for extension in ('.h', '.m'): + templates = [name + extension] + file_with_suffix = file.with_suffix(extension) + tasks.append(self.process_common(skip, overwrite, file_with_suffix, data, templates)) + + await asyncio.gather(*tasks) + + async def process_common(self, skip, overwrite, file_with_suffix, data, templates): + """ + + :param skip: + :param overwrite: + :param file_with_suffix: + :param data: + :param templates: + :return: + """ + if file_with_suffix.is_file(): + if skip: + self.logger.info('Skipping %s', file_with_suffix.name) + return + if overwrite: + self.logger.info('Overriding %s', file_with_suffix.name) + self.write_file(file_with_suffix, templates, data) + else: + while True: + try: + confirm = input('File already exists {}. Overwrite? Y/Enter = yes, N = no\n' + .format(file_with_suffix.name)) + if confirm.lower() == 'y' or not confirm: + self.logger.info('Overriding %s', file_with_suffix.name) + self.write_file(file_with_suffix, templates, data) + break + if confirm.lower() == 'n': + self.logger.info('Skipping %s', file_with_suffix.name) + break + except KeyboardInterrupt: + print('\nThe user interrupted the execution of the program') + sys.exit(1) + else: + self.logger.info('Writing new %s', file_with_suffix.name) + self.write_file(file_with_suffix, templates, data) + + @staticmethod + def filter_pattern(interface, pattern): + """ + + :param interface: + :param pattern: + :return: + """ + names = tuple(interface.enums.keys()) + tuple(interface.structs.keys()) + + if pattern: + match = {key: OrderedDict() for key in vars(interface).keys()} + match['params'] = interface.params + for key, value in vars(interface).items(): + if key == 'params': + continue + match[key].update({name: item for name, item in value.items() if re.match(pattern, item.name)}) + return Interface(**match), names + return interface, names + + async def parser(self, source_xml, source_xsd): + """ + + :param source_xml: + :param source_xsd: + :return: + """ + try: + start = datetime.now() + model = self.loop.run_in_executor(None, Parser().parse, source_xml, source_xsd) + model = await model + self.logger.debug('finish getting model in %s milisec,', (datetime.now() - start).microseconds / 1000.0) + return model + except ParseError as error1: + self.logger.error(error1) + sys.exit(1) + + async def get_all_async(self, source_xml, source_xsd): + """ + + :param source_xml: + :param source_xsd: + :return: + """ + return await asyncio.gather(self.parser(source_xml, source_xsd), self.get_paths(), self.get_mappings()) + + def main(self): + """ + Entry point for parser and generator + :return: None + """ + args = self.get_parser() + self.config_logging(args.verbose) + self.versions_compatibility_validating() + self.output_directory = args.output_directory + + interface, paths, mappings = self.loop.run_until_complete(self.get_all_async(args.source_xml, args.source_xsd)) + + self.env = [args.templates_directory] + [join(args.templates_directory, k) for k in vars(interface).keys()] + + filtered, names = self.filter_pattern(interface, args.regex_pattern) + + tasks = [] + functions_transformer = FunctionsProducer(paths, names, mappings) + if args.enums and filtered.enums: + tasks.append(self.process_main(args.skip, args.overwrite, filtered.enums, + EnumsProducer(paths.enum_class, mappings))) + if args.structs and filtered.structs: + tasks.append(self.process_main(args.skip, args.overwrite, filtered.structs, + StructsProducer(paths.struct_class, names, mappings))) + if args.functions and filtered.functions: + tasks.append(self.process_main(args.skip, args.overwrite, filtered.functions, functions_transformer)) + tasks.append(self.process_function_name(args.skip, args.overwrite, interface.functions, + interface.structs, functions_transformer)) + if tasks: + self.loop.run_until_complete(asyncio.wait(tasks)) + else: + self.logger.warning('Nothing matched with "%s"', args.regex_pattern) + + self.loop.close() + + +if __name__ == '__main__': + Generator().main() diff --git a/generator/mapping.json b/generator/mapping.json new file mode 100644 index 000000000..e90187f04 --- /dev/null +++ b/generator/mapping.json @@ -0,0 +1,11 @@ +{ + "enums": { + "FunctionID": { + "template": true + } + }, + "structs": { + }, + "functions": { + } +} \ No newline at end of file diff --git a/generator/paths.ini b/generator/paths.ini new file mode 100644 index 000000000..9ea5bb800 --- /dev/null +++ b/generator/paths.ini @@ -0,0 +1,7 @@ +ENUM_CLASS = SDLEnum +STRUCT_CLASS = SDLRPCStruct +REQUEST_CLASS = SDLRPCRequest +RESPONSE_CLASS = SDLRPCResponse +NOTIFICATION_CLASS = SDLRPCNotification +FUNCTION_NAMES = SDLRPCFunctionNames +PARAMETER_NAMES = SDLRPCParameterNames \ No newline at end of file diff --git a/generator/requirements.txt b/generator/requirements.txt new file mode 100644 index 000000000..12a4e48f1 --- /dev/null +++ b/generator/requirements.txt @@ -0,0 +1,5 @@ +xmlschema +pylint +Jinja2 +coverage +pathlib2 \ No newline at end of file diff --git a/generator/rpc_spec b/generator/rpc_spec new file mode 160000 index 000000000..bf1466206 --- /dev/null +++ b/generator/rpc_spec @@ -0,0 +1 @@ +Subproject commit bf14662066e3d9c2e7b32b56f1fa8e9dad8dceb1 diff --git a/generator/templates/SDLRPCFunctionNames.h b/generator/templates/SDLRPCFunctionNames.h new file mode 100644 index 000000000..18de36e12 --- /dev/null +++ b/generator/templates/SDLRPCFunctionNames.h @@ -0,0 +1,26 @@ +{% include 'copyright.txt' %} +// SDLRPCFunctionNames.h + +#import "SDLEnum.h" + +/** + * All RPC request / response / notification names + */ +typedef SDLEnum SDLRPCFunctionName SDL_SWIFT_ENUM; +{% for param in params %} +{%- if param.description or param.since %} +/** + {%- if param.description %} + {%- for d in param.description %} + * {{d}} + {%- endfor %}{% endif -%} + {%- if param.description and param.since %} + * + {%- endif %} + {%- if param.since %} + * @since SDL {{param.since}} + {%- endif %} + */ +{%- endif %} +extern SDLRPCFunctionName const SDLRPCFunctionName{{ param.name }}; +{% endfor -%} \ No newline at end of file diff --git a/generator/templates/SDLRPCFunctionNames.m b/generator/templates/SDLRPCFunctionNames.m new file mode 100644 index 000000000..2ae5f21a6 --- /dev/null +++ b/generator/templates/SDLRPCFunctionNames.m @@ -0,0 +1,7 @@ +{% include 'copyright.txt' %} +// SDLRPCFunctionNames.m + +#import "SDLRPCFunctionNames.h" +{% for param in params %} +SDLRPCFunctionName const SDLRPCFunctionName{{ param.name }} = @"{{ param.origin }}"; +{%- endfor %} diff --git a/generator/templates/SDLRPCParameterNames.h b/generator/templates/SDLRPCParameterNames.h new file mode 100644 index 000000000..4a00ce029 --- /dev/null +++ b/generator/templates/SDLRPCParameterNames.h @@ -0,0 +1,14 @@ +{% include 'copyright.txt' %} +// SDLRPCParameterNames.h + +#import +#import "SDLMacros.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* SDLRPCParameterName SDL_SWIFT_ENUM; +{% for param in params %} +extern SDLRPCParameterName const SDLRPCParameterName{{ param.name }}; +{%- endfor %} + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/generator/templates/SDLRPCParameterNames.m b/generator/templates/SDLRPCParameterNames.m new file mode 100644 index 000000000..ae6bdace7 --- /dev/null +++ b/generator/templates/SDLRPCParameterNames.m @@ -0,0 +1,12 @@ +{% include 'copyright.txt' %} +// SDLRPCParameterNames.h + +#import "NSMutableDictionary+Store.h" +#import "SDLRPCParameterNames.h" + +NS_ASSUME_NONNULL_BEGIN +{% for param in params %} +SDLRPCParameterName const SDLRPCParameterName{{ param.name }} = @"{{ param.origin }}"; +{%- endfor %} + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h new file mode 100644 index 000000000..c8a99cfd1 --- /dev/null +++ b/generator/templates/base_struct_function.h @@ -0,0 +1,93 @@ +{% include 'copyright.txt' %} +// {{name}}.h +{% block imports %} +{%- for import in imports.enum %} +#import "{{import}}.h" +{%- endfor %} +{%- if imports.struct %} +{% endif -%} +{%- for import in imports.struct %} +@class {{import}}; +{%- endfor %} +{%- endblock %} +{%- if deprecated and deprecated is sameas true -%} +{%- set ending = ' __deprecated' -%} +{%- elif deprecated and deprecated is string -%} +{%- set ending = ' __deprecated_msg("{}")'.format(deprecated) -%} +{%- endif %} + +NS_ASSUME_NONNULL_BEGIN +{% if description or since %} +/** + {%- if description %} + {%- for d in description %} + * {{d}} + {%- endfor %}{% endif -%} + {%- if description and since %} + * + {%- endif %} + {%- if deprecated %} + * @deprecated + {%- endif %} + {%- if since %} + * @since SDL {{since}} + {%- endif %} + */ +{%- endif %} +@interface {{name}} : {{extends_class}}{{ending}} +{%- block constructors %} +{% for c in constructors %} +/** + {%- if c.description %} + {%- for d in c.description %} + * {{d}} + {%- endfor %} + * + {%- endif %} + {%- for a in c.all %} + * @param {{a.variable}} - {{a.constructor_argument}} + {%- endfor %} + * @return A {{name}} object + */ +{%- if c.deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} +- (instancetype)initWith{{c.init}}; +{%- if c.deprecated %} +#pragma clang diagnostic pop +{%- endif %} +{% endfor -%} +{%- endblock -%} +{%- block methods %} +{%- for param in params %} +/** + {%- if param.description %} + {%- for d in param.description %} + * {{d}} + {%- endfor %}{% endif -%} + {%- if param.description and param.since %} + * + {%- endif %} + {%- if param.deprecated %} + * @deprecated + {%- endif %} + {%- if param.since %} + * @since SDL {{param.since}} + {%- endif %} + {%- if param.description or param.since %} + * + {%- endif %} + * {{'Required, ' if param.mandatory else 'Optional, '}}{{param.type_native}} + */ +{%- if param.deprecated and param.deprecated is sameas true -%} +{%- set ending = ' __deprecated' -%} +{%- elif param.deprecated and param.deprecated is string -%} +{%- set ending = ' __deprecated_msg("{}")'.format(param.deprecated) -%} +{%- endif %} +@property ({{'nullable, ' if not param.mandatory}}{{param.modifier}}, nonatomic) {{param.type_sdl}}{{param.origin}}{{ending}}; +{% endfor -%} +{%- endblock %} +@end + +NS_ASSUME_NONNULL_END diff --git a/generator/templates/base_struct_function.m b/generator/templates/base_struct_function.m new file mode 100644 index 000000000..addc1cb77 --- /dev/null +++ b/generator/templates/base_struct_function.m @@ -0,0 +1,65 @@ +{% include 'copyright.txt' %} +// {{name}}.m +{%- block imports %} +#import "{{name}}.h" +#import "NSMutableDictionary+Store.h" +{%- for import in imports %} +#import "{{import}}.h" +{%- endfor %} +{%- endblock %} + +NS_ASSUME_NONNULL_BEGIN + +@implementation {{name}} +{% block constructors %} +{%- for c in constructors %} +- (instancetype)initWith{{c.init}} { +{%- if c.deprecated %} + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} + self = [{{'self' if c.self else 'super'}} init{{'With'+c.self if c.self}}]; +{%- if c.deprecated %} + #pragma clang diagnostic pop +{%- endif %} + if (!self) { + return nil; + } + {%- for a in c.arguments %} + {%- if a.deprecated %} + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + {%- endif %} + self.{{a.origin}} = {{a.constructor_argument}}; + {%- if a.deprecated %} + #pragma clang diagnostic pop + {%- endif %} + {%- endfor %} + return self; +} +{% endfor -%} +{% endblock -%} +{%- block methods %} +{%- for p in params %} +{%- if p.deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} +- (void)set{{p.origin|title}}:({{'nullable ' if not p.mandatory}}{{p.type_generic}}{{p.type_sdl|trim}}){{p.origin}} { + [self.{{parameters_store}} sdl_setObject:{{p.origin}} forName:SDLRPCParameterName{{p.method_suffix}}]; +} + +- ({{'nullable ' if not p.mandatory}}{{p.type_generic}}{{p.type_sdl|trim}}){{p.origin}} { + {% if p.mandatory -%} + NSError *error = nil; + {% endif -%} + return [self.{{parameters_store}} sdl_{{p.for_name}}ForName:SDLRPCParameterName{{p.method_suffix}}{{' ofClass:'+p.of_class if p.of_class}} error:{{'&error' if p.mandatory else 'nil'}}]; +} +{%- if p.deprecated %} +#pragma clang diagnostic pop +{%- endif %} +{% endfor %} +{%- endblock %} +@end + +NS_ASSUME_NONNULL_END diff --git a/generator/templates/copyright.txt b/generator/templates/copyright.txt new file mode 100644 index 000000000..abb41f0da --- /dev/null +++ b/generator/templates/copyright.txt @@ -0,0 +1,31 @@ +/* +* Copyright (c) {{year}}, SmartDeviceLink Consortium, Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the +* distribution. +* +* Neither the name of the SmartDeviceLink Consortium Inc. nor the names of +* its contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ \ No newline at end of file diff --git a/generator/templates/enums/FunctionID.h b/generator/templates/enums/FunctionID.h new file mode 100644 index 000000000..92e36ac29 --- /dev/null +++ b/generator/templates/enums/FunctionID.h @@ -0,0 +1,29 @@ +{% include 'copyright.txt' %} +// {{name}}.h + +#import +#import "NSNumber+NumberType.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A function ID for an SDL RPC +@interface {{name}} : NSObject + +/// The shared object for pulling function id information ++ (instancetype)sharedInstance; + +/// Gets the function name for a given SDL RPC function ID +/// +/// @param functionID A function ID +/// @returns An SDLRPCFunctionName +- (nullable SDLRPCFunctionName)functionNameForId:(UInt32)functionID; + +/// Gets the function ID for a given SDL RPC function name +/// +/// @param functionName The RPC function name +- (nullable NSNumber *)functionIdForName:(SDLRPCFunctionName)functionName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/generator/templates/enums/FunctionID.m b/generator/templates/enums/FunctionID.m new file mode 100644 index 000000000..92ddacafc --- /dev/null +++ b/generator/templates/enums/FunctionID.m @@ -0,0 +1,52 @@ +{% include 'copyright.txt' %} +// {{name}}.m + +#import "{{name}}.h" + +#import "NSMutableDictionary+Store.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface {{name}} () + +@property (nonatomic, strong, nonnull) NSDictionary* functionIds; + +@end + +@implementation {{name}} + ++ (instancetype)sharedInstance { + static {{name}}* functionId = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + functionId = [[{{name}} alloc] init]; + }); + return functionId; +} + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + self.functionIds = @{ + {%- for param in params %} + @{{param.value}}: SDLRPCFunctionName{{param.name}}{{ ',' if not loop.last }} + {%- endfor %} + }; + return self; +} + +- (nullable SDLRPCFunctionName)functionNameForId:(UInt32)functionID { + return self.functionIds[@(functionID)]; +} + +- (nullable NSNumber *)functionIdForName:(SDLRPCFunctionName)functionName { + return [[self.functionIds allKeysForObject:functionName] firstObject]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h new file mode 100644 index 000000000..f3ee55c9d --- /dev/null +++ b/generator/templates/enums/template.h @@ -0,0 +1,52 @@ +{% include 'copyright.txt' %} +// {{ name }}.h +{% block imports -%} +{%- for import in imports %} +#import "{{import}}.h" +{%- endfor %} +{%- endblock -%} +{%- if deprecated and deprecated is sameas true -%} +{%- set ending = ' __deprecated' -%} +{%- elif deprecated and deprecated is string -%} +{%- set ending = ' __deprecated_msg("{}")'.format(deprecated) -%} +{%- endif %} +{%- block body %} +{% if description or since %} +/** + {%- if description %} + {%- for d in description %} + * {{d}} + {%- endfor %}{% endif -%} + {%- if description and since %} + * + {%- endif %} + {%- if deprecated %} + * @deprecated + {%- endif %} + {%- if since %} + * @since SDL {{since}} + {%- endif %} + */ +{%- endif %} +typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; +{% for param in params %} +{%- if param.description or param.since %} +/** + {%- if param.description %} + {%- for d in param.description %} + * {{d}} + {%- endfor %}{% endif -%} + {%- if param.description and param.since %} + * + {%- endif %} + {%- if param.deprecated %} + * @deprecated + {%- endif %} + {%- if param.since %} + * @since SDL {{param.since}} + {%- endif %} + */ +{%- endif %} +extern {{ name }} const {{ name }}{{param.name}}{{ ' %s%s%s'|format('NS_SWIFT_NAME(', param.name|lower, ')') if NS_SWIFT_NAME is defined}}; +{% endfor -%} +{% endblock -%} \ No newline at end of file diff --git a/generator/templates/enums/template.m b/generator/templates/enums/template.m new file mode 100644 index 000000000..7e669c293 --- /dev/null +++ b/generator/templates/enums/template.m @@ -0,0 +1,12 @@ +{% include 'copyright.txt' %} +// {{ name }}.m + +#import "{{name}}.h" +{%- block body %} +{% if add_typedef %} +typedef SDLEnum {{name}} SDL_SWIFT_ENUM; +{% endif -%} +{%- for param in params %} +{{ name }} const {{ name }}{{param.name}} = @"{{param.origin}}"; +{%- endfor %} +{% endblock -%} \ No newline at end of file diff --git a/generator/templates/functions/template.h b/generator/templates/functions/template.h new file mode 100644 index 000000000..51f426c26 --- /dev/null +++ b/generator/templates/functions/template.h @@ -0,0 +1 @@ +{% extends "base_struct_function.h" %} \ No newline at end of file diff --git a/generator/templates/functions/template.m b/generator/templates/functions/template.m new file mode 100644 index 000000000..3e5e4bd1c --- /dev/null +++ b/generator/templates/functions/template.m @@ -0,0 +1,18 @@ +{% extends "base_struct_function.m" %} +{% block imports %} +{{super()}} +#import "SDLRPCParameterNames.h" +#import "SDLRPCFunctionNames.h" +{%- endblock %} +{% block constructors %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionName{{origin}}])) { + } + return self; +} +#pragma clang diagnostic pop +{{super()}} +{%- endblock -%} +{% set parameters_store = 'parameters' %} diff --git a/generator/templates/structs/template.h b/generator/templates/structs/template.h new file mode 100644 index 000000000..51f426c26 --- /dev/null +++ b/generator/templates/structs/template.h @@ -0,0 +1 @@ +{% extends "base_struct_function.h" %} \ No newline at end of file diff --git a/generator/templates/structs/template.m b/generator/templates/structs/template.m new file mode 100644 index 000000000..c81f652cf --- /dev/null +++ b/generator/templates/structs/template.m @@ -0,0 +1,6 @@ +{% extends "base_struct_function.m" %} +{% block imports %} +{{super()}} +#import "SDLRPCParameterNames.h" +{%- endblock %} +{% set parameters_store = 'store' %} \ No newline at end of file diff --git a/generator/test/__init__.py b/generator/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/generator/test/runner.py b/generator/test/runner.py new file mode 100644 index 000000000..62cb20b54 --- /dev/null +++ b/generator/test/runner.py @@ -0,0 +1,54 @@ +""" +All tests +""" +import logging +import sys +from pathlib import Path +from unittest import TestLoader, TestSuite, TextTestRunner + +ROOT = Path(__file__).absolute() + +sys.path.append(ROOT.parents[1].joinpath('rpc_spec/InterfaceParser').as_posix()) +sys.path.append(ROOT.parents[1].as_posix()) + +try: + from test_enums import TestEnumsProducer + from test_functions import TestFunctionsProducer + from test_structs import TestStructsProducer + from test_CodeFormatAndQuality import TestCodeFormatAndQuality +except ImportError as error: + print('{}.\nProbably you did not initialize submodule'.format(error)) + sys.exit(1) + + +def config_logging(): + """ + :return: None + """ + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%m-%d %H:%M')) + root_logger = logging.getLogger() + handler.setLevel(logging.DEBUG) + root_logger.setLevel(logging.DEBUG) + root_logger.addHandler(handler) + + +def main(): + """ + :return: None + """ + config_logging() + suite = TestSuite() + + suite.addTests(TestLoader().loadTestsFromTestCase(TestFunctionsProducer)) + suite.addTests(TestLoader().loadTestsFromTestCase(TestStructsProducer)) + suite.addTests(TestLoader().loadTestsFromTestCase(TestEnumsProducer)) + suite.addTests(TestLoader().loadTestsFromTestCase(TestCodeFormatAndQuality)) + + runner = TextTestRunner(verbosity=2) + runner.run(suite) + + +if __name__ == '__main__': + main() diff --git a/generator/test/test_CodeFormatAndQuality.py b/generator/test/test_CodeFormatAndQuality.py new file mode 100755 index 000000000..070e1c36e --- /dev/null +++ b/generator/test/test_CodeFormatAndQuality.py @@ -0,0 +1,37 @@ +# pylint: disable=C0103, C0301, C0115, C0116 +"""Interface model unit test + +""" +import unittest +from os import walk +from os.path import join +from pathlib import Path + +from pylint.lint import Run + + +class TestCodeFormatAndQuality(unittest.TestCase): + MINIMUM_SCORE = 9 + + def setUp(self): + """Searching for all python files to be checked + + """ + self.list_of_files = ['--max-line-length=130', '--disable=import-error'] + root = Path(__file__).absolute().parents[1] + for (directory, _, filenames) in walk(root.as_posix()): + self.list_of_files += [join(directory, file) for file in filenames + if file.endswith('.py') and not file.startswith('test') + and 'rpc_spec' not in directory] + + def test_pylint_conformance(self): + """Performing checks by PyLint + + """ + results = Run(self.list_of_files, do_exit=False) + score = results.linter.stats['global_note'] + self.assertGreaterEqual(score, self.MINIMUM_SCORE) + + +if __name__ == '__main__': + unittest.main() diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py new file mode 100644 index 000000000..6efa64f15 --- /dev/null +++ b/generator/test/test_enums.py @@ -0,0 +1,56 @@ +from collections import OrderedDict, defaultdict +from unittest import TestCase + +from model.enum import Enum +from model.enum_element import EnumElement +from transformers.enums_producer import EnumsProducer + + +class TestEnumsProducer(TestCase): + def setUp(self): + self.maxDiff = None + + self.producer = EnumsProducer('SDLEnum', defaultdict(dict)) + + def test_FunctionID(self): + elements = OrderedDict() + elements['RESERVED'] = EnumElement(name='RESERVED', value=0) + elements['RegisterAppInterfaceID'] = EnumElement(name='RegisterAppInterfaceID', hex_value=1) + elements['PerformAudioPassThruID'] = EnumElement(name='PerformAudioPassThruID', hex_value=10) + + item = Enum(name='FunctionID', elements=elements) + expected = OrderedDict() + expected['origin'] = 'FunctionID' + expected['name'] = 'SDLFunctionID' + expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} + expected['params'] = ( + self.producer.param_named(description=[], name='Reserved', origin='RESERVED', since=None, value=0), + self.producer.param_named(description=[], name='RegisterAppInterface', origin='RegisterAppInterfaceID', + since=None, value=None), + self.producer.param_named(description=[], name='PerformAudioPassThru', origin='PerformAudioPassThruID', + since=None, value=None),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) + + def test_TextFieldName(self): + elements = OrderedDict() + elements['SUCCESS'] = EnumElement(name='SUCCESS') + elements['mainField1'] = EnumElement(name='mainField1') + elements['H264'] = EnumElement(name='H264') + elements['UNSUPPORTED_REQUEST'] = EnumElement(name='UNSUPPORTED_REQUEST') + item = Enum(name='TextFieldName', elements=elements) + + expected = OrderedDict() + expected['origin'] = 'TextFieldName' + expected['name'] = 'SDLTextFieldName' + expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} + expected['params'] = ( + self.producer.param_named(description=[], name='Success', origin='SUCCESS', since=None, value=None), + self.producer.param_named(description=[], name='MainField1', origin='mainField1', since=None, value=None), + self.producer.param_named(description=[], name='H264', origin='H264', since=None, value=None), + self.producer.param_named(description=[], name='UnsupportedRequest', origin='UNSUPPORTED_REQUEST', + since=None, value=None)) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) diff --git a/generator/test/test_functions.py b/generator/test/test_functions.py new file mode 100644 index 000000000..fdc6ee9d3 --- /dev/null +++ b/generator/test/test_functions.py @@ -0,0 +1,351 @@ +import re +from collections import namedtuple, OrderedDict, defaultdict +from unittest import TestCase + +from model.array import Array +from model.boolean import Boolean +from model.enum import Enum +from model.enum_element import EnumElement +from model.float import Float +from model.function import Function +from model.integer import Integer +from model.param import Param +from model.string import String +from model.struct import Struct +from transformers.functions_producer import FunctionsProducer + + +class TestFunctionsProducer(TestCase): + def setUp(self): + self.maxDiff = None + + Paths = namedtuple('Paths', 'request_class response_class notification_class function_names parameter_names') + paths = Paths(request_class='SDLRPCRequest', + response_class='SDLRPCResponse', + notification_class='SDLRPCNotification', + function_names='SDLRPCFunctionNames', + parameter_names='SDLRPCParameterNames') + + names = ('FileType', 'Language', 'SyncMsgVersion', 'TemplateColorScheme', 'TTSChunk', 'Choice') + self.producer = FunctionsProducer(paths, names, defaultdict(dict)) + + def test_process_function_name(self): + functions = { + 'RegisterAppInterface': Function(name='RegisterAppInterface', + function_id=EnumElement(name='RegisterAppInterfaceID'), since='3.0.0', + message_type=EnumElement(name='request'), + description=['RegisterAppInterface description'], params={ + 'syncMsgVersion': Param(name='syncMsgVersion', param_type=Float(), since='3.5.0', + description=['syncMsgVersion description'])}), + 'OnHMIStatus': Function(name='OnHMIStatus', function_id=EnumElement(name='OnHMIStatusID'), since='4.0.0', + message_type=EnumElement(name='notification'), + description=['OnHMIStatus description'], params={ + 'acEnable': Param(name='acEnable', param_type=Integer(), since='4.5.0', + description=['acEnable description'])})} + structs = { + 'SoftButton': Struct(name='SoftButton', members={ + 'image': Param(name='image', param_type=String(), since='1.0.0', description=['image description']), + 'ignore': Param(name='ignore', param_type=Struct(name='ignore'))}), + 'PresetBankCapabilities': Struct(name='PresetBankCapabilities', members={ + 'availableHdChannelsAvailable': Param(name='availableHdChannelsAvailable', param_type=Boolean(), + since='2.0.0', + description=['availableHDChannelsAvailable description'])})} + + expected = [ + self.producer.common_names( + description=['OnHMIStatus description'], name='OnHMIStatus', origin='OnHMIStatus', since='4.0.0'), + self.producer.common_names( + description=['RegisterAppInterface description'], name='RegisterAppInterface', + origin='RegisterAppInterface', since='3.0.0')] + actual = self.producer.get_function_names(functions) + self.assertListEqual(expected, actual['params']) + + expected = [ + self.producer.common_names(description=['acEnable description'], name='AcEnable', + origin='acEnable', since='4.5.0'), + self.producer.common_names(description=['availableHDChannelsAvailable description'], + since='2.0.0', name='AvailableHdChannelsAvailable', + origin='availableHdChannelsAvailable'), + self.producer.common_names(description=[], name='Ignore', origin='ignore', since=None), + self.producer.common_names(description=['image description'], name='Image', origin='image', since='1.0.0'), + self.producer.common_names(description=[], name='PresetBankCapabilities', origin='PresetBankCapabilities', since=None), + self.producer.common_names(description=[], name='SoftButton', origin='SoftButton', since=None), + self.producer.common_names(description=['syncMsgVersion description'], name='SyncMsgVersion', + origin='syncMsgVersion', since='3.5.0')] + actual = self.producer.get_simple_params(functions, structs) + self.assertListEqual(expected, actual['params']) + + def test_RegisterAppInterfaceRequest(self): + params = OrderedDict() + params['syncMsgVersion'] = Param( + name='syncMsgVersion', param_type=Struct(name='SyncMsgVersion', description=['Specifies the'], members={ + 'majorVersion': Param(name='majorVersion', param_type=Integer())}), description=['See SyncMsgVersion'], + is_mandatory=True) + params['fullAppID'] = Param(name='fullAppID', description=['ID used'], param_type=String(), is_mandatory=False) + params['dayColorScheme'] = Param( + name='dayColorScheme', param_type=Struct(name='TemplateColorScheme', description=[ + '\n A color scheme for all display layout templates.\n ']), is_mandatory=False) + params['ttsName'] = Param( + name='ttsName', description=['\n TTS string for'], is_mandatory=False, + param_type=Array(element_type=Struct(name='TTSChunk', description=['A TTS chunk']))) + params['isMediaApplication'] = Param( + name='isMediaApplication', param_type=Boolean(), + description=['\n Indicates if the application is a media or a '], is_mandatory=True) + + item = Function(name='RegisterAppInterface', function_id=EnumElement(name='RegisterAppInterfaceID'), + since='1.0.0', + description=['\n Establishes an interface with a mobile application.\n ' + 'Before registerAppInterface no other commands will be accepted/executed.\n '], + message_type=EnumElement(name='request'), params=params) + expected = OrderedDict() + expected['origin'] = 'RegisterAppInterface' + expected['name'] = 'SDLRegisterAppInterface' + expected['extends_class'] = 'SDLRPCRequest' + expected['imports'] = { + '.h': {'enum': {'SDLRPCRequest'}, 'struct': {'SDLTemplateColorScheme', 'SDLTTSChunk', 'SDLSyncMsgVersion'}}, + '.m': {'SDLTemplateColorScheme', 'SDLTTSChunk', 'SDLSyncMsgVersion'}} + expected['description'] = ['Establishes an interface with a mobile application. Before registerAppInterface no ' + 'other commands will be', 'accepted/executed.'] + expected['since'] = '1.0.0' + expected['params'] = ( + self.producer.param_named( + constructor_argument='syncMsgVersion', constructor_argument_override=None, + constructor_prefix='SyncMsgVersion', deprecated=False, description=['See SyncMsgVersion'], + for_name='object', mandatory=True, method_suffix='SyncMsgVersion', modifier='strong', + of_class='SDLSyncMsgVersion.class', origin='syncMsgVersion', since=None, + type_native='SDLSyncMsgVersion *', type_sdl='SDLSyncMsgVersion *'), + self.producer.param_named( + constructor_argument='fullAppID', constructor_argument_override=None, constructor_prefix='FullAppID', + deprecated=False, description=['ID used', + '{"default_value": null, "max_length": null, "min_length": null}'], + for_name='object', mandatory=False, method_suffix='FullAppID', modifier='strong', + of_class='NSString.class', origin='fullAppID', since=None, type_native='NSString *', + type_sdl='NSString *'), + self.producer.param_named( + constructor_argument='dayColorScheme', constructor_argument_override=None, mandatory=False, + constructor_prefix='DayColorScheme', deprecated=False, description=[], for_name='object', + method_suffix='DayColorScheme', modifier='strong', of_class='SDLTemplateColorScheme.class', + origin='dayColorScheme', since=None, type_native='SDLTemplateColorScheme *', + type_sdl='SDLTemplateColorScheme *'), + self.producer.param_named( + constructor_argument='ttsName', constructor_argument_override=None, constructor_prefix='TtsName', + deprecated=False, description=['TTS string for'], for_name='objects', mandatory=False, + method_suffix='TtsName', modifier='strong', of_class='SDLTTSChunk.class', origin='ttsName', since=None, + type_native='NSArray *', type_sdl='NSArray *'), + self.producer.param_named( + constructor_argument='isMediaApplication', constructor_argument_override=None, + constructor_prefix='IsMediaApplication', deprecated=False, + description=['Indicates if the application is a media or a'], for_name='object', mandatory=True, + method_suffix='IsMediaApplication', modifier='strong', of_class='NSNumber.class', + origin='isMediaApplication', since=None, type_native='BOOL', type_sdl='NSNumber *')) + + mandatory_arguments = [ + self.producer.argument_named(variable='syncMsgVersion', deprecated=False, origin='syncMsgVersion', + constructor_argument='syncMsgVersion'), + self.producer.argument_named(variable='isMediaApplication', deprecated=False, origin='isMediaApplication', + constructor_argument='@(isMediaApplication)')] + not_mandatory_arguments = [ + self.producer.argument_named(variable='fullAppID', deprecated=False, origin='fullAppID', + constructor_argument='fullAppID'), + self.producer.argument_named(variable='dayColorScheme', deprecated=False, origin='dayColorScheme', + constructor_argument='dayColorScheme'), + self.producer.argument_named(variable='ttsName', deprecated=False, origin='ttsName', + constructor_argument='ttsName')] + mandatory_init = 'SyncMsgVersion:(SDLSyncMsgVersion *)syncMsgVersion ' \ + 'isMediaApplication:(BOOL)isMediaApplication' + + expected['constructors'] = ( + self.producer.constructor_named( + all=mandatory_arguments, arguments=mandatory_arguments, deprecated=False, + init=mandatory_init, self=''), + self.producer.constructor_named( + all=mandatory_arguments + not_mandatory_arguments, arguments=not_mandatory_arguments, deprecated=False, + init=mandatory_init + ' fullAppID:(nullable NSString *)fullAppID dayColorScheme:(nullable ' + 'SDLTemplateColorScheme *)dayColorScheme ttsName:(nullable NSArray *)ttsName', + self=re.sub(r'([\w\d]+:)\([\w\d\s<>*]*\)([\w\d]+\s*)', r'\1\2', mandatory_init))) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) + + def test_RegisterAppInterfaceResponse(self): + params = OrderedDict() + params['success'] = Param(name='success', param_type=Boolean(), description=[' True if '], is_mandatory=False) + params['language'] = Param(name='language', is_mandatory=False, param_type=Enum(name='Language', elements={ + 'EN-US': EnumElement(name='EN-US', description=['English - US'])}), description=['The currently']) + params['supportedDiagModes'] = Param( + name='supportedDiagModes', is_mandatory=False, description=['\n Specifies the'], + param_type=Array(element_type=Integer(max_value=255, min_value=0), max_size=100, min_size=1)) + params['hmiZoneCapabilities'] = Param(name='hmiZoneCapabilities', is_mandatory=False, + param_type=Array(element_type=Enum(name='HmiZoneCapabilities'), + max_size=100, min_size=1)) + + item = Function(name='RegisterAppInterface', function_id=EnumElement(name='RegisterAppInterfaceID'), + description=['The response '], message_type=EnumElement(name='response'), params=params) + + expected = OrderedDict() + expected['origin'] = 'RegisterAppInterface' + expected['name'] = 'SDLRegisterAppInterfaceResponse' + expected['extends_class'] = 'SDLRPCResponse' + expected['imports'] = {'.h': {'enum': {'SDLRPCResponse', 'SDLLanguage'}, 'struct': set()}, + '.m': {'SDLLanguage'}} + expected['description'] = ['The response'] + expected['params'] = ( + self.producer.param_named( + constructor_argument='language', constructor_argument_override=None, constructor_prefix='Language', + deprecated=False, description=['The currently'], for_name='enum', mandatory=False, + method_suffix='Language', modifier='strong', of_class='', origin='language', + since=None, type_native='SDLLanguage ', type_sdl='SDLLanguage '), + self.producer.param_named( + constructor_argument='supportedDiagModes', constructor_argument_override=None, + constructor_prefix='SupportedDiagModes', deprecated=False, description=['Specifies the'], + for_name='objects', mandatory=False, method_suffix='SupportedDiagModes', modifier='strong', + of_class='NSNumber.class', origin='supportedDiagModes', since=None, + type_native='NSArray *> *', type_sdl='NSArray *> *'), + self.producer.param_named( + constructor_argument='hmiZoneCapabilities', constructor_argument_override=None, + constructor_prefix='HmiZoneCapabilities', deprecated=False, description=[], for_name='enums', + mandatory=False, method_suffix='HmiZoneCapabilities', modifier='strong', + of_class='', origin='hmiZoneCapabilities', since=None, + type_native='NSArray *', type_sdl='NSArray *')) + + arguments = [self.producer.argument_named( + variable='language', deprecated=False, origin='language', constructor_argument='language'), + self.producer.argument_named( + variable='supportedDiagModes', deprecated=False, origin='supportedDiagModes', + constructor_argument='supportedDiagModes'), + self.producer.argument_named( + variable='hmiZoneCapabilities', deprecated=False, origin='hmiZoneCapabilities', + constructor_argument='hmiZoneCapabilities')] + + expected['constructors'] = ( + self.producer.constructor_named( + all=arguments, arguments=arguments, deprecated=False, + init='Language:(nullable SDLLanguage)language supportedDiagModes:(nullable NSArray *>' + ' *)supportedDiagModes hmiZoneCapabilities:(nullable NSArray *)' + 'hmiZoneCapabilities', + self=''),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) + + def test_OnHMIStatus(self): + item = Function(name='OnHMIStatus', function_id=EnumElement(name='OnHMIStatusID'), + message_type=EnumElement(name='notification'), params={ + 'hmiLevel': Param(name='hmiLevel', param_type=Enum(name='HMILevel')) + }) + expected = OrderedDict() + expected['origin'] = 'OnHMIStatus' + expected['name'] = 'SDLOnHMIStatus' + expected['extends_class'] = 'SDLRPCNotification' + expected['imports'] = { + ".h": {'enum': {'SDLRPCNotification'}, 'struct': set()}, + ".m": set() + } + expected['params'] = ( + self.producer.param_named( + constructor_argument='hmiLevel', constructor_argument_override=None, constructor_prefix='HmiLevel', + deprecated=False, description=[], for_name='enum', mandatory=True, method_suffix='HmiLevel', + modifier='strong', of_class='', origin='hmiLevel', since=None, + type_native='SDLHMILevel ', type_sdl='SDLHMILevel '),) + + arguments = [self.producer.argument_named(variable='hmiLevel', deprecated=False, origin='hmiLevel', + constructor_argument='hmiLevel')] + + expected['constructors'] = (self.producer.constructor_named( + all=arguments, arguments=arguments, deprecated=False, self='', init='HmiLevel:(SDLHMILevel)hmiLevel'),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) + + def test_CreateWindow(self): + params = OrderedDict() + params['windowID'] = Param(name='windowID', param_type=Integer()) + params['cmdID'] = Param(name='cmdID', param_type=Integer(max_value=2000000000, min_value=0)) + params['position'] = Param(name='position', param_type=Integer(default_value=1000, max_value=1000, min_value=0)) + params['speed'] = Param(name='speed', param_type=Float(max_value=700.0, min_value=0.0)) + params['offset'] = Param(name='offset', param_type=Integer(max_value=100000000000, min_value=0)) + item = Function(name='CreateWindow', function_id=EnumElement(name='CreateWindowID'), + message_type=EnumElement(name='request'), params=params) + + expected = OrderedDict() + expected['origin'] = 'CreateWindow' + expected['name'] = 'SDLCreateWindow' + expected['extends_class'] = 'SDLRPCRequest' + expected['imports'] = {'.m': set(), '.h': {'struct': set(), 'enum': {'SDLRPCRequest'}}} + expected['params'] = ( + self.producer.param_named( + constructor_argument='windowID', constructor_argument_override=None, constructor_prefix='WindowID', + deprecated=False, description=['{"default_value": null, "max_value": null, "min_value": null}'], + for_name='object', mandatory=True, method_suffix='WindowID', modifier='strong', + of_class='NSNumber.class', origin='windowID', since=None, type_native='UInt32', + type_sdl='NSNumber *'), + self.producer.param_named( + constructor_argument='cmdID', constructor_argument_override=None, constructor_prefix='CmdID', + deprecated=False, description=['{"default_value": null, "max_value": 2000000000, "min_value": 0}'], + for_name='object', mandatory=True, method_suffix='CmdID', modifier='strong', of_class='NSNumber.class', + origin='cmdID', since=None, type_native='UInt32', type_sdl='NSNumber *'), + self.producer.param_named( + constructor_argument='position', constructor_argument_override=None, constructor_prefix='Position', + deprecated=False, description=['{"default_value": 1000, "max_value": 1000, "min_value": 0}'], + for_name='object', mandatory=True, method_suffix='Position', modifier='strong', + of_class='NSNumber.class', origin='position', since=None, type_native='UInt16', + type_sdl='NSNumber *'), + self.producer.param_named( + constructor_argument='speed', constructor_argument_override=None, constructor_prefix='Speed', + deprecated=False, description=['{"default_value": null, "max_value": 700.0, "min_value": 0.0}'], + for_name='object', mandatory=True, method_suffix='Speed', modifier='strong', of_class='NSNumber.class', + origin='speed', since=None, type_native='float', type_sdl='NSNumber *'), + self.producer.param_named( + constructor_argument='offset', constructor_argument_override=None, constructor_prefix='Offset', + deprecated=False, description=['{"default_value": null, "max_value": 100000000000, "min_value": 0}'], + for_name='object', mandatory=True, method_suffix='Offset', modifier='strong', of_class='NSNumber.class', + origin='offset', since=None, type_native='UInt64', type_sdl='NSNumber *')) + + expected_arguments = [self.producer.argument_named(variable='windowID', deprecated=False, origin='windowID', + constructor_argument='@(windowID)'), + self.producer.argument_named(variable='cmdID', deprecated=False, origin='cmdID', + constructor_argument='@(cmdID)'), + self.producer.argument_named(variable='position', deprecated=False, origin='position', + constructor_argument='@(position)'), + self.producer.argument_named(variable='speed', deprecated=False, origin='speed', + constructor_argument='@(speed)'), + self.producer.argument_named(variable='offset', deprecated=False, origin='offset', + constructor_argument='@(offset)')] + + expected['constructors'] = (self.producer.constructor_named( + all=expected_arguments, arguments=expected_arguments, deprecated=False, self='', + init='WindowID:(UInt32)windowID cmdID:(UInt32)cmdID position:(UInt16)position speed:(float)speed ' + 'offset:(UInt64)offset'),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) + + def test_CreateInteractionChoiceSet(self): + params = OrderedDict() + params['choiceSet'] = Param(name='choiceSet', param_type=Array(element_type=Struct(name='Choice'))) + item = Function(name='CreateInteractionChoiceSet', function_id=EnumElement(name='CreateInteractionChoiceSetID'), + message_type=EnumElement(name='request'), params=params) + + expected = OrderedDict() + expected['origin'] = 'CreateInteractionChoiceSet' + expected['name'] = 'SDLCreateInteractionChoiceSet' + expected['extends_class'] = 'SDLRPCRequest' + expected['imports'] = {'.m': {'SDLChoice'}, '.h': {'struct': {'SDLChoice'}, 'enum': {'SDLRPCRequest'}}} + expected['params'] = ( + self.producer.param_named( + constructor_argument='choiceSet', constructor_argument_override=None, + constructor_prefix='ChoiceSet', deprecated=False, description=[], for_name='objects', mandatory=True, + method_suffix='ChoiceSet', modifier='strong', of_class='SDLChoice.class', origin='choiceSet', + since=None, type_native='NSArray *', type_sdl='NSArray *'),) + + argument = [ + self.producer.argument_named(variable='choiceSet', deprecated=False, constructor_argument='choiceSet', + origin='choiceSet')] + + expected['constructors'] = (self.producer.constructor_named( + all=argument, arguments=argument, deprecated=False, self='', + init='ChoiceSet:(NSArray *)choiceSet'),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py new file mode 100644 index 000000000..a1ee10f46 --- /dev/null +++ b/generator/test/test_structs.py @@ -0,0 +1,72 @@ +import re +from collections import OrderedDict, defaultdict +from unittest import TestCase + +from model.param import Param +from model.string import String +from model.struct import Struct +from transformers.structs_producer import StructsProducer + + +class TestStructsProducer(TestCase): + def setUp(self): + self.maxDiff = None + + self.producer = StructsProducer('SDLRPCStruct', ['Image'], defaultdict(dict)) + + def test_Version(self): + version = self.producer.get_version + self.assertIsNotNone(version) + self.assertTrue(re.match(r'^\d*\.\d*\.\d*$', version)) + + def test_CloudAppProperties(self): + item = Struct(name='CloudAppProperties', members={ + 'appID': Param(name='appID', param_type=String()) + }) + expected = OrderedDict() + expected['origin'] = 'CloudAppProperties' + expected['name'] = 'SDLCloudAppProperties' + expected['extends_class'] = 'SDLRPCStruct' + expected['imports'] = {'.m': set(), '.h': {'enum': {'SDLRPCStruct'}, 'struct': set()}} + expected['params'] = ( + self.producer.param_named( + constructor_argument='appID', constructor_argument_override=None, constructor_prefix='AppID', + deprecated=False, description=['{"default_value": null, "max_length": null, "min_length": null}'], + for_name='object', mandatory=True, method_suffix='AppID', modifier='strong', of_class='NSString.class', + origin='appID', since=None, type_native='NSString *', type_sdl='NSString *', ),) + + argument = [ + self.producer.argument_named(variable='appID', deprecated=False, constructor_argument='appID', origin='appID')] + + expected['constructors'] = (self.producer.constructor_named( + all=argument, arguments=argument, deprecated=False, self='', + init='AppID:(NSString *)appID'),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) + + def test_not_mandatory_NS_DESIGNATED_INITIALIZER(self): + item = Struct(name='CloudAppProperties', members={ + 'appID': Param(name='appID', param_type=String(), is_mandatory=False) + }) + expected = OrderedDict() + expected['origin'] = 'CloudAppProperties' + expected['name'] = 'SDLCloudAppProperties' + expected['extends_class'] = 'SDLRPCStruct' + expected['imports'] = {'.m': set(), '.h': {'enum': {'SDLRPCStruct'}, 'struct': set()}} + expected['params'] = ( + self.producer.param_named( + constructor_argument='appID', constructor_argument_override=None, constructor_prefix='AppID', + deprecated=False, description=['{"default_value": null, "max_length": null, "min_length": null}'], + for_name='object', mandatory=False, method_suffix='AppID', modifier='strong', of_class='NSString.class', + origin='appID', since=None, type_native='NSString *', type_sdl='NSString *', ),) + + argument = [ + self.producer.argument_named(variable='appID', deprecated=False, constructor_argument='appID', origin='appID')] + + expected['constructors'] = (self.producer.constructor_named( + all=argument, arguments=argument, deprecated=False, self='', + init='AppID:(nullable NSString *)appID'),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) diff --git a/generator/transformers/__init__.py b/generator/transformers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py new file mode 100644 index 000000000..9ac9f5084 --- /dev/null +++ b/generator/transformers/common_producer.py @@ -0,0 +1,536 @@ +""" +Common transformer +""" +import json +import logging +import re +import textwrap +from abc import ABC +from collections import OrderedDict, namedtuple +from pprint import pformat + +from model.array import Array +from model.boolean import Boolean +from model.enum import Enum +from model.float import Float +from model.function import Function +from model.integer import Integer +from model.param import Param +from model.string import String +from model.struct import Struct + + +class InterfaceProducerCommon(ABC): + """ + Common transformer + """ + + version = '1.0.0' + + def __init__(self, container_name, mapping, names=()): + self.logger = logging.getLogger(self.__class__.__name__) + self.container_name = container_name + self.mapping = mapping + self.names = names + self.param_named = namedtuple('param_named', + 'origin constructor_argument constructor_prefix deprecated mandatory since ' + 'method_suffix of_class type_native type_sdl modifier for_name description ' + 'constructor_argument_override') + self.constructor_named = namedtuple('constructor', 'init self arguments all deprecated') + self.argument_named = namedtuple('argument', 'origin constructor_argument variable deprecated') + + @property + def get_version(self): + """ + + :return: + """ + return self.version + + def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: + """ + + :param item: + :param render: + :return: + """ + if item.description: + render['description'] = self.extract_description(item.description) + if item.since: + render['since'] = item.since + if item.deprecated and item.deprecated.lower() == 'true': + render['deprecated'] = True + + render['params'] = OrderedDict() + + for param in getattr(item, self.container_name).values(): + render['params'][param.name] = self.extract_param(param) + if isinstance(item, (Struct, Function)): + self.extract_imports(param, render['imports']) + + self.custom_mapping(render) + + if 'constructors' not in render and isinstance(item, (Struct, Function)): + designated_initializer = render['designated_initializer'] if 'designated_initializer' in render else False + render['constructors'] = self.extract_constructors(render['params'], designated_initializer) + + render['params'] = tuple(render['params'].values()) + return render + + def extract_imports(self, param: Param, imports): + """ + + :param param: + :param imports: + :return: + """ + + def evaluate(element): + if isinstance(element, (Struct, Enum)): + return element.name, type(element).__name__.lower() + return None, None + + if isinstance(param.param_type, Array): + type_origin, kind = evaluate(param.param_type.element_type) + else: + type_origin, kind = evaluate(param.param_type) + + if type_origin in self.names: + name = 'SDL' + type_origin + imports['.h'][kind].add(name) + imports['.m'].add(name) + + return imports + + @staticmethod + def title(name): + """ + + :param name: + :return: + """ + return name[:1].upper() + name[1:] + + @staticmethod + def minimize_first(name): + """ + + :param name: + :return: + """ + return name[:1].lower() + name[1:] + + @staticmethod + def extract_description(data, length: int = 113) -> list: + """ + Evaluate, align and delete @TODO + :param data: list with description + :param length: + :return: evaluated string + """ + if not data: + return [] + if isinstance(data, list): + data = ' '.join(data) + return textwrap.wrap(re.sub(r'(\s{2,}|\n|\[@TODO.+)', ' ', data).strip(), length) + + @staticmethod + def nullable(type_native, mandatory): + """ + + :param type_native: + :param mandatory: + :return: + """ + if mandatory or re.match(r'\w*Int\d*|BOOL|float|double', type_native): + return '' + return 'nullable ' + + @staticmethod + def wrap(item): + """ + + :param item: + :return: + """ + if getattr(item, 'constructor_argument_override', None) is not None: + return item.constructor_argument_override + if re.match(r'\w*Int\d*|BOOL|float|double', item.type_native): + return '@({})'.format(item.constructor_argument) + return item.constructor_argument + + def extract_constructor(self, data: list, mandatory: bool) -> dict: + """ + + :param data: + :param mandatory: + :return: + """ + data = list(data) + # deprecated = any([m.deprecated for m in data if hasattr(m, 'deprecated')]) + + first = data.pop(0) + init = ['{}:({}{}){}'.format(self.title(first.constructor_prefix), + self.nullable(first.type_native, mandatory), + first.type_native.strip(), first.constructor_argument)] + arguments = [self.argument_named(origin=first.origin, constructor_argument=self.wrap(first), + variable=first.constructor_argument, deprecated=first.deprecated)] + for param in data: + arguments.append(self.argument_named(origin=param.origin, constructor_argument=self.wrap(param), + variable=param.constructor_argument, deprecated=param.deprecated)) + init_str = '{}:({}{}){}'.format(self.minimize_first(param.constructor_prefix), + self.nullable(param.type_native, mandatory), + param.type_native.strip(), param.constructor_argument) + init.append(init_str) + + return {'init': ' '.join(init), 'self': '', 'arguments': arguments, 'all': arguments, 'deprecated': False} + + def extract_constructors(self, data: dict, designated_initializer: bool = None) -> tuple: + """ + + :param data: + :param designated_initializer: + :return: + """ + mandatory = [] + not_mandatory = [] + for param in data.values(): + if param.mandatory: + mandatory.append(param) + else: + not_mandatory.append(param) + + result = [] + if mandatory: + mandatory = self.extract_constructor(mandatory, True) + else: + mandatory = OrderedDict() + + if not_mandatory: + not_mandatory = self.extract_constructor(not_mandatory, False) + if mandatory: + not_mandatory['init'] = '{} {}'.format(mandatory['init'], self.minimize_first(not_mandatory['init'])) + not_mandatory['all'] = mandatory['arguments'] + not_mandatory['arguments'] + not_mandatory['deprecated'] = any([m.deprecated for m in mandatory if hasattr(m, 'deprecated')]) + not_mandatory['self'] = re.sub(r'([\w\d]+:)\([\w\d\s<>*]*\)([\w\d]+\s*)', r'\1\2', mandatory['init']) + if not mandatory and designated_initializer: + not_mandatory['init'] = not_mandatory['init'] + ' NS_DESIGNATED_INITIALIZER' + result.append(self.constructor_named(**not_mandatory)) + + if mandatory: + if designated_initializer: + mandatory['init'] = mandatory['init'] + ' NS_DESIGNATED_INITIALIZER' + result.insert(0, self.constructor_named(**mandatory)) + + return tuple(result) + + @staticmethod + def evaluate_type(instance) -> dict: + """ + + :param instance: + :return: + """ + data = OrderedDict() + if isinstance(instance, Enum): + data['for_name'] = 'enum' + data['of_class'] = '' + else: + data['for_name'] = 'object' + if isinstance(instance, (Struct, Enum)): + data['type_sdl'] = 'SDL' + instance.name + data['type_native'] = data['type_sdl'] = 'SDL{} '.format(instance.name) + if isinstance(instance, Struct): + data['of_class'] = 'SDL{}.class'.format(instance.name) + data['type_native'] = data['type_sdl'] = 'SDL{} *'.format(instance.name) + elif isinstance(instance, (Integer, Float)): + if isinstance(instance, Float): + data['type_sdl'] = 'SDLFloat' + data['type_native'] = 'float' + if isinstance(instance, Integer): + if not instance.max_value: + data['type_native'] = 'UInt32' + elif instance.max_value <= 255: + data['type_native'] = 'UInt8' + elif instance.max_value <= 65535: + data['type_native'] = 'UInt16' + elif instance.max_value <= 4294967295: + data['type_native'] = 'UInt32' + elif instance.max_value > 4294967295: + data['type_native'] = 'UInt64' + if instance.min_value is None or instance.min_value < 0: + data['type_sdl'] = 'SDLInt' + elif instance.min_value >= 0: + data['type_sdl'] = 'SDLUInt' + data['of_class'] = 'NSNumber.class' + data['type_sdl'] = 'NSNumber<{}> *'.format(data['type_sdl']) + elif isinstance(instance, String): + data['of_class'] = 'NSString.class' + data['type_sdl'] = data['type_native'] = 'NSString *' + elif isinstance(instance, Boolean): + data['of_class'] = 'NSNumber.class' + data['type_native'] = 'BOOL' + data['type_sdl'] = 'NSNumber *' + return data + + def extract_type(self, param: Param) -> dict: + """ + + :param param: + :return: + """ + + if isinstance(param.param_type, Array): + data = self.evaluate_type(param.param_type.element_type) + data['for_name'] = data['for_name'] + 's' + data['type_sdl'] = data['type_native'] = 'NSArray<{}> *'.format(data['type_sdl'].strip()) + return data + return self.evaluate_type(param.param_type) + + @staticmethod + def param_origin_change(name): + """ + + :param name: + :return: + """ + return {'origin': name, + 'constructor_argument': name, + 'constructor_prefix': InterfaceProducerCommon.title(name), + 'method_suffix': InterfaceProducerCommon.title(name)} + + def extract_param(self, param: Param): + """ + + :param param: + :return: + """ + data = {'constructor_argument_override': None, + 'description': self.extract_description(param.description), + 'since': param.since, + 'mandatory': param.is_mandatory, + 'deprecated': json.loads(param.deprecated.lower()) if param.deprecated else False, + 'modifier': 'strong'} + if isinstance(param.param_type, (Integer, Float, String)): + data['description'].append(json.dumps(vars(param.param_type), sort_keys=True)) + + data.update(self.extract_type(param)) + data.update(self.param_origin_change(param.name)) + return self.param_named(**data) + + @staticmethod + def minimize_last_id(name): + """ + + :param name: + :return: + """ + if name.upper().endswith('ID'): + return name[:-1] + name[-1:].lower() + return name + + @staticmethod + def remove_asterisk(data: dict): + """ + + :param data: + :return: + """ + for key, value in data.copy().items(): + if key.startswith('type') and value.endswith('*'): + if value.startswith('NSArray') and value.endswith('*> *'): + data[key] = value[:-5] + '> *' + else: + data[key] = value[:-1] + + @staticmethod + def add_asterisk(data: dict): + """ + + :param data: + :return: + """ + for key, value in data.copy().items(): + if key.startswith('type') and not value.endswith('*'): + data[key] = value.strip() + ' *' + + def evaluate_param(self, data, name): + """ + + :param name: + :param data: + :return: + """ + param_name = data['origin'] if 'origin' in data else '' + redundant = list(set(data.keys()) - set(self.param_named._fields)) + if redundant: + self.logger.error('%s/%s, redundant attributes (%s)', name, param_name, redundant) + return False + missed = list(set(self.param_named._fields) - set(data.keys())) + if missed: + self.logger.error('%s/%s, missed attributes (%s)', name, param_name, missed) + return False + return True + + def custom_mapping(self, render): + """ + + :param render: + :return: + """ + key = render['origin'] + if 'extends_class' in render: + key += render['extends_class'].replace('SDLRPC', '') + if key in self.mapping: + custom = self.mapping[key].copy() + elif render['origin'] in self.mapping: + custom = self.mapping[render['origin']].copy() + else: + return + self.logger.debug('%s fount in mapping', render['origin']) + + if 'params_title' in custom and custom['params_title'] is False: + for name, custom_data in render['params'].items(): + if re.match(r'^[A-Z\d]+$', custom_data.origin): + render['params'][name] = render['params'][name]._replace(name=custom_data.origin) + del custom['params_title'] + + if 'remove_asterisk' in custom: + for name, custom_data in render['params'].items(): + data = custom_data._asdict() + self.remove_asterisk(data) + render['params'][name] = custom_data._replace(**data) + del custom['remove_asterisk'] + + if 'description' in custom: + render['description'] = self.extract_description(custom['description']) + del custom['description'] + + if '-params' in custom: + for name in custom['-params']: + if name in render['params']: + imp = render['params'][name].of_class.replace('.class', '') + if imp in render['imports']['.m']: + render['imports']['.m'].remove(imp) + for kind in ('struct', 'enum'): + if imp in render['imports']['.h'][kind]: + render['imports']['.h'][kind].remove(imp) + del render['params'][name] + del custom['-params'] + + if 'minimize_last_id' in custom: + for name, custom_data in render['params'].items(): + if name.upper().endswith('ID'): + render['params'][name] = custom_data._replace( + constructor_argument=self.minimize_last_id(custom_data.constructor_argument), + constructor_prefix=self.minimize_last_id(custom_data.constructor_prefix), + method_suffix=self.minimize_last_id(custom_data.method_suffix)) + del custom['minimize_last_id'] + + if 'sort_params' in custom: + render['params'] = OrderedDict(sorted(render['params'].items())) + del custom['sort_params'] + + if 'template' in custom: + if isinstance(custom['template'], bool) and custom['template']: + render['template'] = render['name'][3:] + else: + render['template'] = custom['template'] + del custom['template'] + + if 'maximize_method' in custom: + if isinstance(custom['maximize_method'], str): + for name, custom_data in render['params'].items(): + tmp = re.findall(r'^([a-z]+)(\w*)$', self.minimize_first(custom_data.method_suffix)).pop() + render['params'][name] = custom_data._replace(method_suffix=tmp[0].upper() + tmp[1]) + elif isinstance(custom['maximize_method'], list): + for name in custom['maximize_method']: + if name in render['params']: + custom_data = render['params'][name] + tmp = re.findall(r'^([a-z]+)(\w*)$', self.minimize_first(custom_data.method_suffix)).pop() + render['params'][name] = custom_data._replace(method_suffix=tmp[0].upper() + tmp[1]) + del custom['maximize_method'] + + for key in ('modifier', 'mandatory'): + if key in custom: + for name, custom_data in render['params'].items(): + render['params'][name] = custom_data._replace(**{key: custom[key]}) + del custom[key] + + for key in ('name', 'designated_initializer', 'deprecated', 'NS_ENUM', 'NS_SWIFT_NAME', 'add_typedef'): + if key in custom and isinstance(custom[key], str): + render[key] = custom[key] + del custom[key] + + for name, custom_data in custom.copy().items(): + self.logger.info('applying manual mapping for %s/%s\t%s', render['name'], name, pformat(custom_data)) + if name in render['params']: + if isinstance(custom_data, str): + render['params'][name] = render['params'][name]._replace(name=custom_data) + elif isinstance(custom_data, dict): + data = self.custom_param_update(render['params'][name]._asdict(), custom_data, render['imports']) + if self.evaluate_param(data, render['origin']): + render['params'][name] = self.param_named(**data) + del custom[name] + elif name not in ['sort_constructor']: + if 'description' in custom[name]: + custom[name]['description'] = self.extract_description(custom[name]['description']) + custom[name]['origin'] = name + for key, value in custom[name].copy().items(): + if key.startswith('type') and not value.endswith('*'): + custom[name][key] = value.strip() + ' ' + if self.evaluate_param(custom[name], render['origin']): + render['params'][name] = self.param_named(**custom[name]) + render['params'].move_to_end(name, last=False) + else: + self.logger.warning('For %s provided undefined mapping for "%s": %s, which will be skipped', + render['name'], name, pformat(custom_data)) + del custom[name] + + if 'sort_constructor' in custom: + sorted_params = OrderedDict(sorted(render['params'].items())) + render['constructors'] = self.extract_constructors(sorted_params) + del custom['sort_constructor'] + + @staticmethod + def custom_param_update(data, custom_data, imports) -> dict: + """ + + :param data: + :param custom_data: + :param imports: + :return: + """ + if 'description' in custom_data: + custom_data['description'] = InterfaceProducerCommon.extract_description(custom_data['description']) + if 'minimize_last_id' in custom_data: + data['constructor_argument'] = InterfaceProducerCommon.minimize_last_id(data['constructor_argument']) + data['constructor_prefix'] = InterfaceProducerCommon.minimize_last_id(data['constructor_prefix']) + data['method_suffix'] = InterfaceProducerCommon.minimize_last_id(data['method_suffix']) + if 'maximize_method' in custom_data: + tmp = re.findall(r'^([a-z]+)(\w*)$', InterfaceProducerCommon.minimize_first(data['method_suffix'])).pop() + data['method_suffix'] = tmp[0].upper() + tmp[1] + if 'origin' in custom_data: + data.update(InterfaceProducerCommon.param_origin_change(custom_data['origin'])) + if 'type' in custom_data: + new_type = re.sub(r'NSArray|[\s<>*]', '', custom_data['type']) + data['type_native'] = data['type_sdl'] = re.sub(r'[\s*]', '', custom_data['type']) + ' *' + data['method_suffix'] = new_type + if data['of_class']: + data['of_class'] = new_type + '.class' + if new_type.lower() in map(str.lower, imports['.m']): + imports['.m'] = set(i for i in imports['.m'] if i.lower() != new_type.lower()) + imports['.m'].add(new_type) + for kind in ('enum', 'struct'): + if new_type.lower() in map(str.lower, imports['.h'][kind]): + imports['.h'][kind] = set(i for i in imports['.h'][kind] if i.lower() != new_type.lower()) + imports['.h'][kind].add(new_type) + if 'remove_asterisk' in custom_data: + InterfaceProducerCommon.remove_asterisk(data) + if 'add_asterisk' in custom_data: + InterfaceProducerCommon.add_asterisk(data) + for key, value in custom_data.copy().items(): + if key.startswith('type') and not value.endswith('*'): + custom_data[key] = value.strip() + ' ' + data.update(custom_data) + return data diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py new file mode 100644 index 000000000..9dc8b7834 --- /dev/null +++ b/generator/transformers/enums_producer.py @@ -0,0 +1,71 @@ +""" +Enums transformer +""" +import logging +import re +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 transformer + """ + + def __init__(self, enum_class, mapping=None): + super(EnumsProducer, self).__init__( + container_name='elements', + mapping=mapping.get('enums', {})) + self.enum_class = enum_class + self.logger = logging.getLogger(self.__class__.__name__) + self.param_named = namedtuple('param_named', 'origin description name since value') + + def transform(self, item: Enum, render=None) -> dict: + """ + + :param item: + :param render: + :return: + """ + name = 'SDL{}{}'.format(item.name[:1].upper(), item.name[1:]) + tmp = {self.enum_class} + imports = {'.h': tmp, '.m': tmp} + if not render: + render = OrderedDict() + render['origin'] = item.name + render['name'] = name + render['imports'] = imports + super(EnumsProducer, self).transform(item, render) + return render + + def extract_param(self, param: EnumElement): + """ + + :param param: + :return: + """ + data = {'origin': param.name, 'description': self.extract_description(param.description, 113), + 'since': param.since} + + if re.match(r'^[A-Z]{1,2}\d|\d[A-Z]{1,2}$', param.name): + data['name'] = param.name + elif re.match(r'(^[a-z\d]+$|^[A-Z\d]+$)', param.name): + data['name'] = param.name.title() # .capitalize() + elif re.match(r'^(?=\w*[a-z])(?=\w*[A-Z])\w+$', param.name): + if param.name.endswith('ID'): + data['name'] = param.name[:-2] + else: + data['name'] = param.name[:1].upper() + param.name[1:] + elif re.match(r'^(?=\w*?[a-zA-Z])(?=\w*?[_-])(?=[0-9])?.*$', param.name): + name = [] + for item in re.split('[_-]', param.name): + if re.match(r'^[A-Z\d]+$', item): + name.append(item.title()) + data['name'] = ''.join(name) + + data['value'] = param.value + + return self.param_named(**data) diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py new file mode 100644 index 000000000..d227b62ea --- /dev/null +++ b/generator/transformers/functions_producer.py @@ -0,0 +1,139 @@ +""" +Functions transformer +""" + +import logging +from collections import namedtuple, OrderedDict +from pprint import pformat + +from model.function import Function +from transformers.common_producer import InterfaceProducerCommon + + +class FunctionsProducer(InterfaceProducerCommon): + """ + Functions transformer + """ + + def __init__(self, paths, names, mapping=None): + super(FunctionsProducer, self).__init__( + container_name='params', + names=names, + mapping=mapping.get('functions', {})) + self.request_class = paths.request_class + self.response_class = paths.response_class + self.notification_class = paths.notification_class + self.function_names = paths.function_names + self.parameter_names = paths.parameter_names + self.logger = logging.getLogger(self.__class__.__name__) + self.common_names = namedtuple('common_names', 'name origin description since') + + def transform(self, item: Function, render=None) -> dict: + """ + + :param item: + :param render: + :return: + """ + list(map(item.params.__delitem__, filter(item.params.__contains__, ['success', 'resultCode', 'info']))) + + name = 'SDL' + item.name + imports = {'.h': {'enum': set(), 'struct': set()}, + '.m': set()} + extends_class = None + if item.message_type.name == 'response': + extends_class = self.response_class + name = name + item.message_type.name.capitalize() + 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['.h']['enum'].add(extends_class) + + if not render: + render = OrderedDict() + render['origin'] = item.name + render['name'] = name + render['extends_class'] = extends_class + render['imports'] = imports + + super(FunctionsProducer, self).transform(item, render) + + return render + + def get_function_names(self, items: dict) -> dict: + """ + + :param items: + :return: dict + """ + render = OrderedDict() + for item in items.values(): + tmp = {'name': self.title(item.name), + 'origin': item.name, + 'description': self.extract_description(item.description), + 'since': item.since} + render[item.name] = self.common_names(**tmp) + + self.custom_mapping_names(render, self.function_names) + + return {'params': sorted(render.values(), key=lambda a: a.name)} + + def get_simple_params(self, functions: dict, structs: dict) -> dict: + """ + :param functions: + :param structs: + :return: + """ + + def evaluate(element): + # if isinstance(element.param_type, (Integer, Float, Boolean, String)): + return {element.name: self.common_names(**{ + 'name': self.title(element.name), + 'origin': element.name, + 'description': self.extract_description(element.description), + 'since': element.since})} + # return OrderedDict() + + render = OrderedDict() + + for func in functions.values(): + for param in func.params.values(): + render.update(evaluate(param)) + + for struct in structs.values(): + render.update(evaluate(struct)) + for param in struct.members.values(): + render.update(evaluate(param)) + + self.custom_mapping_names(render, self.parameter_names) + + return {'params': sorted(render.values(), key=lambda a: a.name)} + + def custom_mapping_names(self, render, file_name): + """ + + :param render: + :param file_name: + :return: + """ + if file_name in self.mapping: + self.logger.debug('applying manual mapping for %s\n%s', file_name, pformat(self.mapping[file_name])) + for name, item in self.mapping[file_name].items(): + if isinstance(item, dict) and name in render: + render[name] = render[name]._replace(**item) + elif isinstance(item, list): + for value in item: + data = OrderedDict().fromkeys(self.common_names._fields) + data.update(value) + render[value['name']] = self.common_names(**data) + elif name in render: + render[name] = render[name]._replace(name=item) + elif isinstance(item, dict): + data = OrderedDict().fromkeys(self.common_names._fields) + data.update(item) + render[name] = self.common_names(**data) + else: + render[name] = self.common_names(item, name, '', '') + self.logger.warning('Into %s added name="%s", origin="%s"', self.function_names, item, name) 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..8f48eb8bd --- /dev/null +++ b/generator/transformers/structs_producer.py @@ -0,0 +1,44 @@ +""" +Structs transformer +""" + +import logging +from collections import OrderedDict + +from model.struct import Struct +from transformers.common_producer import InterfaceProducerCommon + + +class StructsProducer(InterfaceProducerCommon): + """ + Structs transformer + """ + + def __init__(self, struct_class, enum_names, mapping=None): + super(StructsProducer, self).__init__( + container_name='members', + names=enum_names, + mapping=mapping.get('structs', {})) + self.struct_class = struct_class + self.logger = logging.getLogger(self.__class__.__name__) + + def transform(self, item: Struct, render=None) -> dict: + """ + + :param item: + :param render: + :return: + """ + name = 'SDL' + item.name + imports = {'.h': {'enum': set(), 'struct': set()}, '.m': set()} + imports['.h']['enum'].add(self.struct_class) + if not render: + render = OrderedDict() + render['origin'] = item.name + render['name'] = name + render['extends_class'] = self.struct_class + render['imports'] = imports + + super(StructsProducer, self).transform(item, render) + + return render From 209f50079767b81206e8526d193bb94e1fdf1be9 Mon Sep 17 00:00:00 2001 From: leonid l lokhmatov Date: Mon, 17 Feb 2020 19:47:56 +0200 Subject: [PATCH 02/28] RPC generator iOS document --- generator/README.md | 2128 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 2120 insertions(+), 8 deletions(-) diff --git a/generator/README.md b/generator/README.md index 623f46d94..60155f4c6 100644 --- a/generator/README.md +++ b/generator/README.md @@ -2,23 +2,53 @@ ## Overview -This script provides the possibility to auto-generate iOS code based on a given SDL MOBILE_API XML specification. +This script provides a possibility to auto-generate Objective-C code (header \*.h and implementation \*.m classes) based on the SDL MOBILE_API XML specification provided as a source of true. ## Requirements -The script requires Python 3.5 pre-installed in the system. This is the minimal Python 3 version that has not reached the end-of-life (https://devguide.python.org/devcycle/#end-of-life-branches). +The script requires **Python 3.5** pre-installed on the host system. This is the minimal **Python 3** version that has not yet reached the end-of-life (https://devguide.python.org/devcycle/#end-of-life-branches). + +Note: two pyton versions can be installed on the system and to use the required third version one must use the **pip3** command instead of the commonly used **pip**. + +All required libraries are listed in `requirements.txt` and should be pre-installed on the system prior to using the sript. Please use the following command to install the libraries: + +```shell script +$ pip3 install -r requirements.txt +``` + +Please also make sure all git submodules are installed and up to date since the script uses the XML parser provided there. + +```shell script +$ git submodule update --init --recursive +``` + +or -Some required libraries are described in `requirements.txt` and should be pre-installed by the command: ```shell script -pip install -r requirements.txt +$ git submodule update --recursive ``` -Please also make sure before usage the 'generator/rpc_spec' Git submodule is successfully initialized, because the script uses the XML parser provided there. + ## Usage + +**Usage example** + ```shell script -usage: runner.py [-h] [-v] [-xml SOURCE_XML] [-xsd SOURCE_XSD] - [-d OUTPUT_DIRECTORY] [-t [TEMPLATES_DIRECTORY]] - [-r REGEX_PATTERN] [--verbose] [-e] [-s] [-m] [-y] [-n] +$ cd sdl_ios +$ python3 generator/generator.py -xml generator/rpc_spec/MOBILE_API.xml -xsd generator/rpc_spec/MOBILE_API.xsd -d output_dir +``` + +*Note: one may skip the first item python3 if the host system configured to run \*.py files on the Python-3 engine.* + +As a result the output_dir will have all the new generated files (at the time of this article writing as many as 716 files were produced). + +**Detailed usage description (keys, options)** + + +``` +usage: generator.py [-h] [-v] [-xml SOURCE_XML] [-xsd SOURCE_XSD] + [-d OUTPUT_DIRECTORY] [-t [TEMPLATES_DIRECTORY]] + [-r REGEX_PATTERN] [--verbose] [-e] [-s] [-m] [-y] [-n] Proxy Library RPC Generator @@ -45,3 +75,2085 @@ optional arguments: -n, --skip skip overwriting of existing files in output directory, ignore confirmation message ``` + +### How to use the generated classes + +Since all RPC classes used in **SmartDeviceLink iOS** library were created manually due to historical reasons the generated files might and will differ from the original ones. The code generator takes all efforts possible to produce source code as close to the original as possible and it already has maps (dictionaries) to rename classes, data structures that do not follow the naming rules though it is not quite possible since the source of true is the XML file and anything that goes beyond it is hard if possible at all to handle. To incorporate the generated files into the project one must use a diff tool (Meld, FileMerge, you name it) and compare the 2 sets of RPC files and check what changed and which changes should go upstream. The last but no least chances are there appear new generated files that are not present in the project. If such a case one must add those files to Xcode project manually and place them in proper groups sorting the files by their kind. Note: the groups are just virtual folders and due to historical reasons the ones do not map to file system folders so phisically all files go to the root folder and when they get added to the project they should go to the following folders: + * Enums + * Structs + * Notification + * Requests + * Responses + +Due to the explosion of complexity and code that is involved with mapping every exception that has ever been made in the iOS project compared to the RPC spec, the decision was made that the generator should not pursue those mappings. This has several implications: + + * The generator will not override the existing RPCs. All newly created structs, enums, requests, responses, and notifications will be generated, but new parameters to existing RPCs will have to be manually handled. The script's `--skip` command line switch will be used. + * Existing unit tests cannot be used to verify the output. + +The idea is that a future major version could involve a switch-over to using all generated code instead of the current code. It is not perfect for the current version but it is helpful for adding new RPCs. +Any major version change cannot be done that would be involved with overwriting existing RPC classes and to map out all the differences would be a huge time and complexity-sink that the PM and PR author agreed was feasible but unmaintainable and undesirable. + +# Objetive-C transformation rules + +## Overview +These are the general transformation rules for SDL RPC classes Objective-C Library. The base classes description already included in the library is not provided here for more one may want to view the source code. + +## Output Directory Structure and Package definitions + +The script creates corresponding RPC classes of ``, `` and `` elements following the `MOBILE_API.xml` rules. According to existing structure of SmartDeviceLink library the output directory will contain the following files (plain structure, no subfolders). For enum, struct, function future naming, generally it should be named + +```SDL``` + + in camel case. + + +**Requests:** + + * SDLxxx.h + * SDLxxx.m + +**Responses:** + + * SDLxxxResponse.h + * SDLxxxResponse.m + +*Note: as an exception from rules this kind has the suffix 'Response'* + +**Structures:** + + * SDLxxx.h + * SDLxxx.m + +**Enums:** + + * SDLxxx.h + * SDLxxx.m + +**Notifications:** + + * SDLxxx.h + * SDLxxx.m + +Where the **xxx** is the correspondent item name. + + +## The License Header + +All files should begin with the license information. + +``` +/* + * Copyright (c) 2017 - [year], SmartDeviceLink Consortium, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the SmartDeviceLink Consortium Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +``` +Where `[year]` in the copyright line is the current (?) year. +*Note: for simplisity sake the header removed from Objective-C code snippets in the following sections of this document* + + +### General rules for Objective-C classes +1. Default initializer in every class +```objc + - (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionNameRegisterAppInterface])) { + } + return self; + } +``` +*Pease note the required double brackets in the if statement* + +2. Initializer for mandatory params if there is/are any in XML (skipped if no params) + +3. Initializer for all params if there is/are any which is not mandatory in XML (skipped if no params) + +## Scalars. +There are 4 type of scalar values declared in the SDL lib. These are: +1. **SDLInt** - A declaration that this NSNumber contains an NSInteger. +0. **SDLUInt** - A declaration that this NSNumber contains an NSUInteger. +0. **SDLBool** - A declaration that this NSNumber contains a BOOL. +0. **SDLFloat** - A declaration that this NSNumber contains a float. +*Note: the ones declared as empty protocols and NSNumber conforms to them.* +```objc +@interface NSNumber (NumberType) +``` +Usage example: +```objc +@property (strong, nonatomic) NSNumber *touchEventId; +``` +or an array: +```objc +@property (strong, nonatomic) NSArray *> *timeStamp; +``` +*Note: the 4 scalar values implemented as empty protocols and 'extend' the* **NSNumber** *class.* + +## Enums +So called enums in **sdl\_ios** implemented as strings (NSString) typedefed with a proper enum type. One may notice it is not real enums but NSString objects though in SWIFT they must become real enums. + +*Example:* + +```objc +typedef NSString* SDLEnum SDL_SWIFT_ENUM; +``` + +*Note: This new defined type has already had an asterisk at the end so anything that inherits from SDLEnum needs no asterisk.* + +```objc +typedef SDLEnum SDLTouchType SDL_SWIFT_ENUM; +``` + +*Note: The compiler considers SDLTouchType as NSString** + +And here is a concrete 'enum' item + +```objc +extern SDLTouchType const SDLTouchTypeBegin; +``` + +If an item is deprecated then it will be declared as such: + +```objc +extern SDLTouchType const SDLTouchTypeBegin __deprecated; +``` + +or even: + +```objc +extern SDLTouchType const SDLTouchTypeBegin __deprecated_msg(("this item is deprecated once and for all, please use SDLTouchTypeNewType instead")); +``` + + +Each Enum class is stored in two files (header \*.h and implementation \*.m) and the filename and the class name should be composed based on the value from the `"name"` attribute of ``. + +Example: + +``` + +./SDLImageType.h +./SDLImageType.m +``` + +Each Enum class should include the enum definition, see for example the AmbientLightStatus enum: + +```objc +#import "SDLEnum.h" +typedef SDLEnum SDLAmbientLightStatus SDL_SWIFT_ENUM; +``` + +Take for an instance the enum class KeypressMode + +```xml + + Enumeration listing possible keyboard events. + + Each keypress is individually sent as the user presses the keyboard keys. + + + The keypresses are queued and a string is eventually sent once the user chooses to submit their entry. + + + The keypresses are queue and a string is sent each time the user presses a keyboard key; the string contains the entire current entry. + + +``` + +In the following example + +```objc +extern SDLKeypressMode const SDLKeypressModeSingleKeypress; +``` + +The **SDLKeypressModeSingleKeypress** enum is seen by the compiler as: + +```objc +extern NSString* const SDLKeypressModeSingleKeypress; +``` + +and **SDLKeypressModeSingleKeypress** itself must be implemented in the correspondent SDLKeypressMode.m file as a string: + +```objc +SDLKeypressMode const SDLKeypressModeSingleKeypress = @"SINGLE_KEYPRESS"; +``` + +## Structures + +Structures in **sdl_ios** implemented as classes derived from the parent class SDLRPCStruct with all parameters implemented as "@property". Let us take for an instance the **DeviceInfo** structure. In the XML it is declared as following: + +```xml + + Various information about connecting device. + + Device model + + + Device firmware revision + + + Device OS + + + Device OS version + + + Device mobile carrier (if applicable) + + + Omitted if connected not via BT. + + + ``` + + *Please note that all params declared as mandatory="false" and there is one init method with all the params in the generated file. The method* ```+ (instancetype)currentDevice;``` *comes from the old manually made implementation* + *All method and property descriptions generated from the xml descriptions* + + *Note: the file begins with the* **NS_ASSUME_NONNULL_BEGIN** *macro which makes all properties / parameters mandatory. If a parameter is not mandatory then the modifier* **nullable** *will be used* + +```objc +// SDLDeviceInfo.h +// + +#import "SDLRPCStruct.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Various information about connecting device. + * + * @since SDL 3.0.0 + */ +@interface SDLDeviceInfo : SDLRPCStruct + +/// Convenience init. Object will contain all information about the connected device automatically. +/// +/// @return An SDLDeviceInfo object ++ (instancetype)currentDevice; + +/** + * @param hardware + * @param firmwareRev + * @param os + * @param osVersion + * @param carrier + * @param @(maxNumberRFCOMMPorts) + * @return A SDLDeviceInfo object + */ +- (instancetype)initWithHardware:(nullable NSString *)hardware firmwareRev:(nullable NSString *)firmwareRev os:(nullable NSString *)os osVersion:(nullable NSString *)osVersion carrier:(nullable NSString *)carrier maxNumberRFCOMMPorts:(UInt8)maxNumberRFCOMMPorts; + +/** + * Device model + * {"default_value": null, "max_length": 500, "min_length": 0} + * + * Optional, NSString * + */ +@property (nullable, strong, nonatomic) NSString *hardware; + +/** + * Device firmware revision + * {"default_value": null, "max_length": 500, "min_length": 0} + * + * Optional, NSString * + */ +@property (nullable, strong, nonatomic) NSString *firmwareRev; + +/** + * Device OS + * {"default_value": null, "max_length": 500, "min_length": 0} + * + * Optional, NSString * + */ +@property (nullable, strong, nonatomic) NSString *os; + +/** + * Device OS version + * {"default_value": null, "max_length": 500, "min_length": 0} + * + * Optional, NSString * + */ +@property (nullable, strong, nonatomic) NSString *osVersion; + +/** + * Device mobile carrier (if applicable) + * {"default_value": null, "max_length": 500, "min_length": 0} + * + * Optional, NSString * + */ +@property (nullable, strong, nonatomic) NSString *carrier; + +/** + * Omitted if connected not via BT. + * {"default_value": null, "max_value": 100, "min_value": 0} + * + * Optional, UInt8 + */ +@property (nullable, strong, nonatomic) NSNumber *maxNumberRFCOMMPorts; + +@end + +NS_ASSUME_NONNULL_END +``` + +The implementation **SDLDeviceInfo.m** file: + +```objc +// SDLDeviceInfo.m +// + +#import "SDLDeviceInfo.h" +#import "NSMutableDictionary+Store.h" +#import "SDLRPCParameterNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLDeviceInfo + + ++ (instancetype)currentDevice { + static SDLDeviceInfo *deviceInfo = nil; + if (deviceInfo == nil) { + deviceInfo = [[SDLDeviceInfo alloc] init]; + deviceInfo.hardware = [UIDevice currentDevice].model; + deviceInfo.os = [UIDevice currentDevice].systemName; + deviceInfo.osVersion = [UIDevice currentDevice].systemVersion; + CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init]; + CTCarrier *carrier = netinfo.subscriberCellularProvider; + NSString *carrierName = carrier.carrierName; + deviceInfo.carrier = carrierName; + } + return deviceInfo; +} + +- (instancetype)initWithHardware:(nullable NSString *)hardware firmwareRev:(nullable NSString *)firmwareRev os:(nullable NSString *)os osVersion:(nullable NSString *)osVersion carrier:(nullable NSString *)carrier maxNumberRFCOMMPorts:(UInt8)maxNumberRFCOMMPorts { + self = [super init]; + if (!self) { + return nil; + } + self.hardware = hardware; + self.firmwareRev = firmwareRev; + self.os = os; + self.osVersion = osVersion; + self.carrier = carrier; + self.maxNumberRFCOMMPorts = @(maxNumberRFCOMMPorts); + return self; +} + +- (void)setHardware:(nullable NSString *)hardware { + [self.store sdl_setObject:hardware forName:SDLRPCParameterNameHardware]; +} + +- (nullable NSString *)hardware { + return [self.store sdl_objectForName:SDLRPCParameterNameHardware ofClass:NSString.class error:nil]; +} + +- (void)setFirmwareRev:(nullable NSString *)firmwareRev { + [self.store sdl_setObject:firmwareRev forName:SDLRPCParameterNameFirmwareRevision]; +} + +- (nullable NSString *)firmwareRev { + return [self.store sdl_objectForName:SDLRPCParameterNameFirmwareRevision ofClass:NSString.class error:nil]; +} + +- (void)setOs:(nullable NSString *)os { + [self.store sdl_setObject:os forName:SDLRPCParameterNameOS]; +} + +- (nullable NSString *)os { + return [self.store sdl_objectForName:SDLRPCParameterNameOS ofClass:NSString.class error:nil]; +} + +- (void)setOsVersion:(nullable NSString *)osVersion { + [self.store sdl_setObject:osVersion forName:SDLRPCParameterNameOSVersion]; +} + +- (nullable NSString *)osVersion { + return [self.store sdl_objectForName:SDLRPCParameterNameOSVersion ofClass:NSString.class error:nil]; +} + +- (void)setCarrier:(nullable NSString *)carrier { + [self.store sdl_setObject:carrier forName:SDLRPCParameterNameCarrier]; +} + +- (nullable NSString *)carrier { + return [self.store sdl_objectForName:SDLRPCParameterNameCarrier ofClass:NSString.class error:nil]; +} + +- (void)setMaxNumberRFCOMMPorts:(nullable NSNumber *)maxNumberRFCOMMPorts { + [self.store sdl_setObject:maxNumberRFCOMMPorts forName:SDLRPCParameterNameMaxNumberRFCOMMPorts]; +} + +- (nullable NSNumber *)maxNumberRFCOMMPorts { + return [self.store sdl_objectForName:SDLRPCParameterNameMaxNumberRFCOMMPorts ofClass:NSNumber.class error:nil]; +} + +@end + +NS_ASSUME_NONNULL_END +``` +## Functions + +Functions in iOS implemented as 3 different classes grouped by its kind, all the 3 extend the common parent class **SDLRPCMessage** +```objc +// This is the parent class for all the 3 function kinds +SDLRPCMessage : SDLRPCStruct : NSObject + +SDLRPCNotification : SDLRPCMessage /// An RPC sent from the head unit to the app about some data change, such as a button was pressed +SDLRPCRequest : SDLRPCMessage /// Superclass of RPC requests +SDLRPCResponse : SDLRPCMessage /// Superclass of RPC responses +``` +*Note: for some reason SDLRPCMessage conforms to NSCopying protocol twice.* + +First of all there is the **FunctionID** class generated though it is not declared in the XML. This class maps all function IDs that are integers to function names as strings. + +1. Uses of the `"name"` attribute should be normalized by the removal of the ID suffix, e.g. `RegisterAppInterfaceID -> RegisterAppInterface`. + +0. The constant name should be camel case formatted. + +0. The constant has 2 fields the first is the `int` value of the `"value"` attribute and the second is the `String` value of normalized `"name"` attribute. + +Internally it uses another file that lists all the function names **SDLRPCFunctionNames** + +```objc +// SDLFunctionID.h + +#import +#import "NSNumber+NumberType.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A function ID for an SDL RPC +@interface SDLFunctionID : NSObject + +/// The shared object for pulling function id information ++ (instancetype)sharedInstance; + +/// Gets the function name for a given SDL RPC function ID +/// +/// @param functionID A function ID +/// @returns An SDLRPCFunctionName +- (nullable SDLRPCFunctionName)functionNameForId:(UInt32)functionID; + +/// Gets the function ID for a given SDL RPC function name +/// +/// @param functionName The RPC function name +- (nullable NSNumber *)functionIdForName:(SDLRPCFunctionName)functionName; + +@end + +NS_ASSUME_NONNULL_END +``` + +Each from MOBILE_API.XML is declared in SDLRPCFunctionNames.h and SDLRPCFunctionNames.m files. It exports function names as strings. + +```objc +SDLRPCFunctionNames.h +#import "SDLEnum.h" +/** +* All RPC request / response / notification names +*/ +typedef SDLEnum SDLRPCFunctionName SDL_SWIFT_ENUM; + +/// Function name for an AddCommand RPC +extern SDLRPCFunctionName const SDLRPCFunctionNameAddCommand; + +/// Function name for an AddSubMenu RPC +extern SDLRPCFunctionName const SDLRPCFunctionNameAddSubMenu; + +. . . and so on +``` + +And the implementation file SDLRPCFunctionNames.m : + +```objc +// +// SDLRPCFunctionNames.m +// SmartDeviceLink +// + +#import "SDLRPCFunctionNames.h" + +SDLRPCFunctionName const SDLRPCFunctionNameAddCommand = @"AddCommand"; +SDLRPCFunctionName const SDLRPCFunctionNameAddSubMenu = @"AddSubMenu"; + +. . . and so on +``` + + +### Request Functions (SDLRPCRequest) + +#### Without parameters + +The templates from the table below can be used as a basic for each Request class which can be generated from MOBILE_API.XML +Two different file types with (".h" amd ".m" extensions) need to be created for each function in the XML. + +```xml + + + Requests the current list of resident filenames for the registered app. + Not supported on first generation SDL enabled vehicles. + + +``` + +Declaration file (.h) + +```objc +// SDLListFiles.h +// + +#import "SDLRPCRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Requests the current list of resident filenames for the registered app. Not supported on first generation SDL + * enabled vehicles. + * + * @since SDL 3.0.0 + */ +@interface SDLListFiles : SDLRPCRequest + +@end + +NS_ASSUME_NONNULL_END +``` + +Implementation file (.m) + +```objc +// SDLListFiles.m + +#import "SDLListFiles.h" +#import "NSMutableDictionary+Store.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLListFiles + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionNameListFiles])) { + } + return self; +} +#pragma clang diagnostic pop + +@end + +NS_ASSUME_NONNULL_END +``` + + + + + + +##### Function with simple params + +This section depicts all functions which include one or a few parameters of the following types (integer|decimal|boolean|string). Such parameters are considered as simple. + +```xml + + + RPC used to get the current properties of a cloud application + + + +``` + +### Request Functions (SDLRPCRequest) + +The parent class is **SDLRPCRequest** + +```objc +// SDLGetCloudAppProperties.h +// + +#import "SDLRPCRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * RPC used to get the current properties of a cloud application + * + * @since SDL 5.1.0 + */ +@interface SDLGetCloudAppProperties : SDLRPCRequest + +/** + * @param appID + * @return A SDLGetCloudAppProperties object + */ +- (instancetype)initWithAppID:(NSString *)appID; + +/** + * {"default_value": null, "max_length": 100, "min_length": 1} + * + * Required, NSString * + */ +@property (strong, nonatomic) NSString *appID; + +@end + +NS_ASSUME_NONNULL_END +``` + + +```objc +// SDLGetCloudAppProperties.m +// + +#import "SDLGetCloudAppProperties.h" +#import "NSMutableDictionary+Store.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLGetCloudAppProperties + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionNameGetCloudAppProperties])) { + } + return self; +} +#pragma clang diagnostic pop + +- (void)setAppID:(NSString *)appID { + [self.parameters sdl_setObject:appID forName:SDLRPCParameterNameAppId]; +} + +- (NSString *)appID { + NSError *error = nil; + return [self.parameters sdl_objectForName:SDLRPCParameterNameAppId ofClass:NSString.class error:&error]; +} + +@end + +NS_ASSUME_NONNULL_END +``` + +### Response Functions (SDLRPCResponse) + +The parent class is **SDLRPCResponse** + +Simple response example **AddCommand**, with no arguments: + +```xml + + + + true if successful; false, if failed + + + + See Result + + + + + + + + + + + + + + + + Provides additional human readable info regarding the result. + + + +``` + +```objc +// SDLAddCommandResponse.h +// + +#import "SDLRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * @since SDL 1.0.0 + */ +@interface SDLAddCommandResponse : SDLRPCResponse + +@end + +NS_ASSUME_NONNULL_END +``` + +```objc +// SDLAddCommandResponse.m +// + +#import "SDLAddCommandResponse.h" +#import "NSMutableDictionary+Store.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLAddCommandResponse + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionNameAddCommand])) { + } + return self; +} +#pragma clang diagnostic pop + +@end + +NS_ASSUME_NONNULL_END +``` + +As one may notice this class does not implement too much since the main logic is already implemented in its parent class: + +```objc +// SDLRPCResponse.h +// + +#import "SDLRPCMessage.h" +#import "SDLResult.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Superclass of RPC responses +@interface SDLRPCResponse : SDLRPCMessage + +/** + * The correlation id of the corresponding SDLRPCRequest. + */ +@property (strong, nonatomic) NSNumber *correlationID; + +/** + * Whether or not the SDLRPCRequest was successful. + */ +@property (strong, nonatomic) NSNumber *success; + +/** + * The result of the SDLRPCRequest. If the request failed, the result code contains the failure reason. + */ +@property (strong, nonatomic) SDLResult resultCode; + +/** + * More detailed success or error message. + */ +@property (nullable, strong, nonatomic) NSString *info; + +@end + +NS_ASSUME_NONNULL_END +``` + +#### Response class with extra argument example (Alert Response): + +```xml + + + + true if successful; false, if failed + + + + See Result + + + + + + + + + + + + + + + + Provides additional human readable info regarding the result. + + + + + Amount of time (in seconds) that an app must wait before resending an alert. + If provided, another system event or overlay currently has a higher priority than this alert. + An app must not send an alert without waiting at least the amount of time dictated. + + + + +``` + +As we can see it in the XML it requites an additional param **tryAgainTime** which is declared and implemented in the **SDLAlertResponse** class as follows: + +```objc +// SDLAlertResponse.h +// + +#import "SDLRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * @since SDL 1.0.0 + */ +@interface SDLAlertResponse : SDLRPCResponse + +/** + * @param @(tryAgainTime) + * @return A SDLAlertResponse object + */ +- (instancetype)initWithTryAgainTime:(UInt32)tryAgainTime; + +/** + * Amount of time (in seconds) that an app must wait before resending an alert. If provided, another system event or + * overlay currently has a higher priority than this alert. An app must not send an alert without waiting at least + * the amount of time dictated. + * {"default_value": null, "max_value": 2000000000, "min_value": 0} + * + * @since SDL 2.0.0 + * + * Optional, UInt32 + */ +@property (nullable, strong, nonatomic) NSNumber *tryAgainTime; + +@end + +NS_ASSUME_NONNULL_END +``` + +```objc +// SDLAlertResponse.m +// + + +#import "SDLAlertResponse.h" +#import "NSMutableDictionary+Store.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLAlertResponse + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionNameAlert])) { + } + return self; +} +#pragma clang diagnostic pop + +- (void)setTryAgainTime:(nullable NSNumber *)tryAgainTime { + [self.parameters sdl_setObject:tryAgainTime forName:SDLRPCParameterNameTryAgainTime]; +} + +- (nullable NSNumber *)tryAgainTime { + return [self.parameters sdl_objectForName:SDLRPCParameterNameTryAgainTime ofClass:NSNumber.class error:nil]; +} + +@end + +NS_ASSUME_NONNULL_END +``` + +### Notification Functions (SDLRPCNotification) + +The parent class is **SDLRPCNotification** + + +Let us take for an instance the class **SDLOnAudioPassThru**. It is declared in the XML as follows. It does not look as much as anything and so its implementation. + +```xml + + Binary data is in binary part of hybrid msg + +``` + +The corresponding Objective-C class **SDLOnAudioPassThru** looks as following. + +```objc +// SDLOnAudioPassThru.h + +#import "SDLRPCNotification.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Binary data is in binary part of hybrid msg + * + * @since SDL 2.0.0 + */ +@interface SDLOnAudioPassThru : SDLRPCNotification + +@end + +NS_ASSUME_NONNULL_END +``` + +```objc +// SDLOnAudioPassThru.m + +#import "SDLOnAudioPassThru.h" +#import "NSMutableDictionary+Store.h" +#import "SDLRPCParameterNames.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLOnAudioPassThru + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionNameOnAudioPassThru])) { + } + return self; +} +#pragma clang diagnostic pop + +@end + +NS_ASSUME_NONNULL_END +``` + + +Another example is the class **SDLOnAppInterfaceUnregistered**. It is declared in the XML as follows. + +```xml + + + See AppInterfaceUnregisteredReason + + +``` + +The corresponding Objective-C class **SDLOnAppInterfaceUnregistered** looks as following. + +```objc +// SDLOnAppInterfaceUnregistered.h + +#import "SDLRPCNotification.h" + +@class SDLAppInterfaceUnregisteredReason; + +NS_ASSUME_NONNULL_BEGIN + +/** + * @since SDL 1.0.0 + */ +@interface SDLOnAppInterfaceUnregistered : SDLRPCNotification + +/** + * @param reason - @(reason) + * @return A SDLOnAppInterfaceUnregistered object + */ +- (instancetype)initWithReason:(SDLAppInterfaceUnregisteredReason)reason; + +/** + * See AppInterfaceUnregisteredReason + * + * Required, SDLAppInterfaceUnregisteredReason + */ +@property (strong, nonatomic) SDLAppInterfaceUnregisteredReason reason; + +@end + +NS_ASSUME_NONNULL_END +``` + +*Note: though the `SDLAppInterfaceUnregisteredReason reason` declared as a property but implemented as custom getter and setter* + +```objc +// SDLOnAppInterfaceUnregistered.m + +#import "SDLOnAppInterfaceUnregistered.h" +#import "NSMutableDictionary+Store.h" +#import "SDLAppInterfaceUnregisteredReason.h" +#import "SDLRPCParameterNames.h" +#import "SDLRPCFunctionNames.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLOnAppInterfaceUnregistered + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (instancetype)init { + if ((self = [super initWithName:SDLRPCFunctionNameOnAppInterfaceUnregistered])) { + } + return self; +} +#pragma clang diagnostic pop + +- (instancetype)initWithReason:(SDLAppInterfaceUnregisteredReason)reason { + self = [super init]; + if (!self) { + return nil; + } + self.reason = @(reason); + return self; +} + +- (void)setReason:(SDLAppInterfaceUnregisteredReason)reason { + [self.parameters sdl_setObject:reason forName:SDLRPCParameterNameReason]; +} + +- (SDLAppInterfaceUnregisteredReason)reason { + NSError *error = nil; + return [self.parameters sdl_enumForName:SDLRPCParameterNameReason error:&error]; +} + +@end + +NS_ASSUME_NONNULL_END +``` + +# Custom mapping + +There are cases named `edge cases` when it is not possible to get the required info from XML or some manual additions are required in generated classes. For that purpose the generator includes the custom mapping file `mapping.json` that allows to add required customizations. + +## Structure of `mapping.json` + +The customization script contains the JSON object. Below is the schema: + +Enums: +```json5 +{ + ["enums"]: { + [enum_name]: { + "template": [true|custom_template_name], + "name": [custom_enum_namee], + "description": [custom_description_string], + "deprecated": [custom_deprecated_description_string], + "-params": [element_to_delete, param_to_delete, ...], + "params_title": false + [element_name]: { + "origin": [custom_element_name], + "description": [custom_description_string], + "since": [custom_since_version_string], + "value": [custom_value] + } + } + } +} +``` + +Structs and Functions: +```json5 +{ + ["structs"|"functions"]: { + [struct_name|function_name]: { + "template": [true|custom_template_name], + "name": [custom_struct_name|custom_function_name], + "description": [custom_description_string], + "deprecated": [custom_deprecated_description_string], + "sort_constructor": true, + "-params": [element_to_delete, param_to_delete, ...], + [param_name|member_name]: { + "origin": [custom_param_name|custom_member_name], + "constructor_argument": [custom_constructor_argument_name_string], + "constructor_prefix": [custom_constructor_parameter_name_string], + "method_suffix": [custom_forName:SDLRPCParameterName_method_suffix_name_string], + "deprecated": [custom_deprecated_description_string], + "mandatory": [true|false], + "since": [custom_since_version_string], + "of_class": [custom_"ofClass:{{of_class}}"_string], + "type_native": [custom_type_used_in_constructor_string], + "type_sdl": [custom_type_used_in_methods_string], + "modifier": [assign|copy|strong|weak], + "for_name": [custom_"_sdl_{{for_name}}"_name_string], + "description": [custom_description_string], + "constructor_argument_override": [custom_constructor_argument_string], + } + } + } +} +``` + +Root keys in the structure are `"enums"`, `"structs"` and `"functions"`. +On the next level expected `name`, `template`, `description`, `deprecated`, `sort_constructor`, `-params`, `params_title` or corresponding name of required ``, ``, ``. +Then the next level, is set of properties which present in each corresponding structure. +In case when corresponding structure exist not required to list all properties. + +The mapping object provide the possibility to create brand new ``, `` or ``, in this case all corresponding properties should be provided. + +## "template" + +It is possible to create custom template for any corresponding structure (``, ``, ``). + +As template engine used jinja, which has own language, documentation - https://jinja.palletsprojects.com/en/2.11.x/ +like it was done for `sdl_ios/generator/templates/enums/FunctionID.*`, otherwise you can extend existing template and add required functionality. + +There are following block's in default template: +* {% block imports %} +* {% block constructors %} +* {% block methods %} + +You can extend it by adding your custom code and call `{{super()}}` to add code from parent template, example: +```jinja +{%- block imports %} +yout code +{{super()}} +{%- endblock -%} +``` + +To enable custom template feature add following into `sdl_ios/generator/mapping.json`: +```json +{ + "functions": { + "PutFile": { + "template": true + } + } +} +``` + +Create custom template - `sdl_ios/generator/templates/functions/PutFile.h`: +```jinja +{% extends 'functions/template.h' %} +{%- block imports %} +#import +{{super()}} +{%- endblock -%} +{%- block constructors -%} +{{super()}} +- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData; +{% endblock -%} +{%- block methods %} +@property (nullable, copy, nonatomic) SDLRPCCommandNotificationHandler handler; +{{super()}} +{%- endblock -%} +``` + +As a result the generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLPutFile.h`: +```objc +... +#import +... +- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData; +... +@property (nullable, copy, nonatomic) SDLRPCCommandNotificationHandler handler; +... +``` + +Create custom template - `sdl_ios/generator/templates/functions/PutFile.m`: +```jinja +{% extends 'functions/template.h' %} +{%- block imports -%} +{{super()}} + +static NSString *const SDLBundleShortVersionStringKey = @"CFBundleShortVersionString"; +static NSString *const SDLBundleAppNameKey = @"CFBundleName"; +{%- endblock -%} +{%- block constructors -%} +{{super()}} +- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData { + + self = [self initWithFileName:fileName fileType:fileType persistentFile:persistentFile systemFile:systemFile offset:offset length:length crc:[self.class sdl_getCRC32ChecksumForBulkData:bulkData]]; + if (!self) { + return nil; + } + + self.bulkData = bulkData; + + return self; +} + +#pragma mark - Getters and Setters +{% endblock -%} +{%- block methods %} +#pragma mark - Getters / Setters +{{super()}} +-(id)copyWithZone:(nullable NSZone *)zone { + SDLAddCommand *newCommand = [super copyWithZone:zone]; + newCommand->_handler = self.handler; + + return newCommand; +} +{% endblock -%} +``` + +As result generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLPutFile.m`: +```objc +... +static NSString *const SDLBundleShortVersionStringKey = @"CFBundleShortVersionString"; +static NSString *const SDLBundleAppNameKey = @"CFBundleName"; +... +- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData { + + self = [self initWithFileName:fileName fileType:fileType persistentFile:persistentFile systemFile:systemFile offset:offset length:length crc:[self.class sdl_getCRC32ChecksumForBulkData:bulkData]]; + if (!self) { + return nil; + } + + self.bulkData = bulkData; + + return self; +} + +#pragma mark - Getters and Setters +... +-(id)copyWithZone:(nullable NSZone *)zone { + SDLAddCommand *newCommand = [super copyWithZone:zone]; + newCommand->_handler = self.handler; + + return newCommand; +} +... +``` + +## "name" + +Change name of original structure. + +Notice: Name of class and name of file is the the same, so overriding of "name" property aso change the name of target file. + +Example: +```json +{ + "enums": { + "HmiZoneCapabilities": { + "name": "SDLHMIZoneCapabilities" + } + } +} +``` + +`sdl_ios/SmartDeviceLink/SDLHMIZoneCapabilities.m`: +```objc +... +#import "SDLHMIZoneCapabilities.h" +... +SDLHMIZoneCapabilities const SDLHMIZoneCapabilitiesFront = @"FRONT"; +... +``` + +`sdl_ios/SmartDeviceLink/SDLHMIZoneCapabilities.h`: +```objc +... +typedef SDLEnum SDLHMIZoneCapabilities SDL_SWIFT_ENUM; +... +extern SDLHMIZoneCapabilities const SDLHMIZoneCapabilitiesFront; +... +``` + +## "description" + +```json +{ + "functions": { + "RegisterAppInterfaceResponse": { + "description": "long custom description" + } + } +} +``` + +As a result the generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLRegisterAppInterfaceResponse.h`: +```objc +... +/** + * long custom description + */ +@interface SDLRegisterAppInterfaceResponse : SDLRPCResponse +... +``` + +## "deprecated" + +If particular structure marked as deprecated is source xml, e.g.: +```xml + +``` + +By default the generated file will look like the following: +```objc +... +/** + * @deprecated + */ +typedef SDLEnum SDLSupportedSeat SDL_SWIFT_ENUM __deprecated; +... +``` + +But you are able to provide description for deprecated structure: +```json +{ + "enums": { + "SupportedSeat": { + "deprecated": "custom deprecated description" + } + } +} +``` +As a result the generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLSupportedSeat.h`: +```objc +... +/** + * @deprecated + */ +typedef SDLEnum SDLSupportedSeat SDL_SWIFT_ENUM __deprecated_msg("custom deprecated description"); +... +``` + +In case if particular structure wasn't marked as deprecated is source xml and you added "deprecated" for it into "mapping.json", appropriate structure will be marked as deprecated + +## "sort_constructor" + +All "elements|members|params" from particular structure will be transformed into target code in same sequence in which it present in source xml. +By default the appropriate initiator (constructor) will be created from all "elements|members|params" with same sequence in which it present in source xml. + +To sort all "elements|members|params" by alphabetical order and then creating appropriate initiator (constructor) use following mapping parameter: + +```json +{ + "functions": { + "RegisterAppInterfaceResponse": { + "sort_constructor": true + } + } +} +``` + +Before: +```objc +... +/** + * @param syncMsgVersion - syncMsgVersion + * @param language - language + * @param hmiDisplayLanguage - hmiDisplayLanguage + * @param displayCapabilities - displayCapabilities + * @param buttonCapabilities - buttonCapabilities + * @param softButtonCapabilities - softButtonCapabilities + * @param presetBankCapabilities - presetBankCapabilities + * @param hmiZoneCapabilities - hmiZoneCapabilities + * @param speechCapabilities - speechCapabilities + * @param prerecordedSpeech - prerecordedSpeech + * @param vrCapabilities - vrCapabilities + * @param audioPassThruCapabilities - audioPassThruCapabilities + * @param pcmStreamCapabilities - pcmStreamCapabilities + * @param vehicleType - vehicleType + * @param supportedDiagModes - supportedDiagModes + * @param hmiCapabilities - hmiCapabilities + * @param sdlVersion - sdlVersion + * @param systemSoftwareVersion - systemSoftwareVersion + * @param iconResumed - @(iconResumed) + * @return A SDLRegisterAppInterfaceResponse object + */ +- (instancetype)initWithSyncMsgVersion:(nullable SDLSyncMsgVersion *)syncMsgVersion language:(nullable SDLLanguage)language hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage displayCapabilities:(nullable SDLDisplayCapabilities *)displayCapabilities buttonCapabilities:(nullable NSArray *)buttonCapabilities softButtonCapabilities:(nullable NSArray *)softButtonCapabilities presetBankCapabilities:(nullable SDLPresetBankCapabilities *)presetBankCapabilities hmiZoneCapabilities:(nullable NSArray *)hmiZoneCapabilities speechCapabilities:(nullable NSArray *)speechCapabilities prerecordedSpeech:(nullable NSArray *)prerecordedSpeech vrCapabilities:(nullable NSArray *)vrCapabilities audioPassThruCapabilities:(nullable NSArray *)audioPassThruCapabilities pcmStreamCapabilities:(nullable SDLAudioPassThruCapabilities *)pcmStreamCapabilities vehicleType:(nullable SDLVehicleType *)vehicleType supportedDiagModes:(nullable NSArray *> *)supportedDiagModes hmiCapabilities:(nullable SDLHMICapabilities *)hmiCapabilities sdlVersion:(nullable NSString *)sdlVersion systemSoftwareVersion:(nullable NSString *)systemSoftwareVersion iconResumed:(BOOL)iconResumed; +... +``` + +After: +```objc +/** + * @param audioPassThruCapabilities - audioPassThruCapabilities + * @param buttonCapabilities - buttonCapabilities + * @param displayCapabilities - displayCapabilities + * @param hmiCapabilities - hmiCapabilities + * @param hmiDisplayLanguage - hmiDisplayLanguage + * @param hmiZoneCapabilities - hmiZoneCapabilities + * @param iconResumed - @(iconResumed) + * @param language - language + * @param pcmStreamCapabilities - pcmStreamCapabilities + * @param prerecordedSpeech - prerecordedSpeech + * @param presetBankCapabilities - presetBankCapabilities + * @param sdlVersion - sdlVersion + * @param softButtonCapabilities - softButtonCapabilities + * @param speechCapabilities - speechCapabilities + * @param supportedDiagModes - supportedDiagModes + * @param syncMsgVersion - syncMsgVersion + * @param systemSoftwareVersion - systemSoftwareVersion + * @param vehicleType - vehicleType + * @param vrCapabilities - vrCapabilities + * @return A SDLRegisterAppInterfaceResponse object + */ +- (instancetype)initWithAudioPassThruCapabilities:(nullable NSArray *)audioPassThruCapabilities buttonCapabilities:(nullable NSArray *)buttonCapabilities displayCapabilities:(nullable SDLDisplayCapabilities *)displayCapabilities hmiCapabilities:(nullable SDLHMICapabilities *)hmiCapabilities hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage hmiZoneCapabilities:(nullable NSArray *)hmiZoneCapabilities iconResumed:(BOOL)iconResumed language:(nullable SDLLanguage)language pcmStreamCapabilities:(nullable SDLAudioPassThruCapabilities *)pcmStreamCapabilities prerecordedSpeech:(nullable NSArray *)prerecordedSpeech presetBankCapabilities:(nullable SDLPresetBankCapabilities *)presetBankCapabilities sdlVersion:(nullable NSString *)sdlVersion softButtonCapabilities:(nullable NSArray *)softButtonCapabilities speechCapabilities:(nullable NSArray *)speechCapabilities supportedDiagModes:(nullable NSArray *> *)supportedDiagModes syncMsgVersion:(nullable SDLSyncMsgVersion *)syncMsgVersion systemSoftwareVersion:(nullable NSString *)systemSoftwareVersion vehicleType:(nullable SDLVehicleType *)vehicleType vrCapabilities:(nullable NSArray *)vrCapabilities; +``` + +## "-params" + +If you need to remove some "elements|members|params", list them as follow: +```json +{ + "functions": { + "RegisterAppInterfaceResponse": { + "-params": [ + "syncMsgVersion" + ] + } + } +} +``` + +Before: +```objc +... +@class SDLSyncMsgVersion; +... +/** + * @param syncMsgVersion - syncMsgVersion + * @param language - language + * @param hmiDisplayLanguage - hmiDisplayLanguage + * @return A SDLRegisterAppInterfaceResponse object + */ +- (instancetype)initWithSyncMsgVersion:(nullable SDLSyncMsgVersion *)syncMsgVersion language:(nullable SDLLanguage)language hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage; + +@property (nullable, strong, nonatomic) SDLSyncMsgVersion *syncMsgVersion; +... +``` + +After: +```objc +... +/** + * @param language - language + * @param hmiDisplayLanguage - hmiDisplayLanguage + * @return A SDLRegisterAppInterfaceResponse object + */ +- (instancetype)initWithLanguage:(nullable SDLLanguage)language hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage; +... +``` + +As result generated listed "elements|members|params" will be removed from appropriate getters/setters, initiators (constructors) and imports. + +## "params_title" + +Used for Enums only. + +To avoid capitalizing elements (used named from original source XML) use following parameter: +```json +{ + "enums": { + "AudioType": { + "params_title": false + } + } +} +``` + +Before: +```objc +... +extern SDLAudioType const SDLAudioTypePcm; +... +``` + +After: +```objc +.. +extern SDLAudioType const SDLAudioTypePCM; +... +``` + +## Customization sub elements|params|members + +## `Enum` elements customization + +### "value" + +```json +{ + "enums": { + "FunctionID": { + "PerformInteractionID": { + "value": "0XFF" + } + } + } +} +``` + +Before: +```objc +... +@10: SDLRPCFunctionNamePerformInteraction, +... +``` + +After: +```objc +... +@0XFF: SDLRPCFunctionNamePerformInteraction, +... +``` + +## `Struct` members and `Function` params customization + +### "origin" + +changing of "origin" also will affect "constructor_argument", "constructor_prefix" and "method_suffix" + +```json +{ + "structs": { + "RdsData": { + "PS": { + "origin": "programService" + } + } + } +} +``` + +Before: +```objc +... +- (instancetype)initWithPS:(nullable NSString *)PS +... +@property (nullable, strong, nonatomic) NSString *PS; +... +``` + +After: +```objc +... +- (instancetype)initWithProgramService:(nullable NSString *)programService +... +@property (nullable, strong, nonatomic) NSString *programService; +... +``` + +### "constructor_argument" + +initiator (constructor) parameter name + +```json +{ + "structs": { + "KeyboardProperties": { + "limitedCharacterList": { + "constructor_argument": "example_limitedCharacterList" + } + } + } +} +``` + +After: + +sdl_ios/SmartDeviceLink/SDLKeyboardProperties.h +```objc +... +/** + * @param language - language + * @param example_limitedCharacterList - example_limitedCharacterList + * @return A SDLKeyboardProperties object + */ +- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)example_limitedCharacterList; +... +``` + +sdl_ios/SmartDeviceLink/SDLKeyboardProperties.m +```objc +... +- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)example_limitedCharacterList{ + self = [super init]; + if (!self) { + return nil; + } + self.language = language; + self.limitedCharacterList = example_limitedCharacterList; +... +``` + +### "constructor_argument_override" + +```json +{ + "structs": { + "KeyboardProperties": { + "limitedCharacterList": { + "constructor_argument_override": "[limitedCharacterList mutableCopy]" + } + } + } +} +``` + +After: + +sdl_ios/SmartDeviceLink/SDLKeyboardProperties.h +```objc +... +/** + * @param language - language + * @param limitedCharacterList - [limitedCharacterList mutableCopy] + * @return A SDLKeyboardProperties object + */ +- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)limitedCharacterList; +... +``` + +sdl_ios/SmartDeviceLink/SDLKeyboardProperties.m +```objc +... +- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)limitedCharacterList { + self = [super init]; + if (!self) { + return nil; + } + self.language = language; + self.limitedCharacterList = [limitedCharacterList mutableCopy]; +... +``` + +### "constructor_prefix" + +```json +{ + "structs": { + "ImageResolution": { + "resolutionWidth": { + "constructor_prefix": "Width" + } + } + } +} +``` + +Before: +```objc +... +/** + * @param resolutionWidth - @(resolutionWidth) + * @return A SDLImageResolution object + */ +- (instancetype)initWithResolutionWidth:(UInt16)resolutionWidth; +... +``` + +After: +```objc +... +/** + * @param width - @(resolutionWidth) + * @return A SDLImageResolution object + */ +- (instancetype)initWithWidth:(UInt16)resolutionWidth; +... +``` + +### "method_suffix" + +```json +{ + "structs": { + "TouchEvent": { + "c": { + "method_suffix": "Coordinate" + } + } + } +} +``` + +Before: +```objc +... +- (void)setC:(NSArray *)c { + [self.store sdl_setObject:c forName:SDLRPCParameterNameC]; +} + +- (NSArray *)c { + NSError *error = nil; + return [self.store sdl_objectsForName:SDLRPCParameterNameC ofClass:SDLTouchCoord.class error:&error]; +} +... +``` + +After: +```objc +... +- (void)setC:(NSArray *)c { + [self.store sdl_setObject:c forName:SDLRPCParameterNameCoordinate]; +} + +- (NSArray *)c { + NSError *error = nil; + return [self.store sdl_objectsForName:SDLRPCParameterNameCoordinate ofClass:SDLTouchCoord.class error:&error]; +} +... +``` + +### "deprecated" + +```json +{ + "structs": { + "KeyboardProperties": { + "autoCompleteText": { + "deprecated": false + } + } + } +} +``` +```objc +... +@property (nullable, strong, nonatomic) NSString *autoCompleteText; +... +``` +#### +```json +{ + "structs": { + "KeyboardProperties": { + "autoCompleteText": { + "deprecated": "long deprecation description" + } + } + } +} +``` +```objc +... +/** + * @deprecated + */ +@property (nullable, strong, nonatomic) NSString *autoCompleteText __deprecated_msg("long deprecation description"); +... +``` +#### +```json +{ + "structs": { + "KeyboardProperties": { + "autoCompleteText": { + "deprecated": true + } + } + } +} +``` +```objc +... +/** + * @deprecated + */ +@property (nullable, strong, nonatomic) NSString *autoCompleteText __deprecated; +... +``` + +### "mandatory" + +```json +{ + "structs": { + "Image": { + "isTemplate": { + "mandatory": true + } + } + } +} +``` + +Before: +```objc +... +/** + * @param value - value + * @param imageType - imageType + * @return A SDLImage object + */ +- (instancetype)initWithValue:(NSString *)value imageType:(SDLImageType)imageType; + +/** + * @param value - value + * @param imageType - imageType + * @param isTemplate - @(isTemplate) + * @return A SDLImage object + */ +- (instancetype)initWithValue:(NSString *)value imageType:(SDLImageType)imageType isTemplate:(BOOL)isTemplate; +... +/** + * Optional, BOOL + */ +@property (nullable, strong, nonatomic) NSNumber *isTemplate; +... +``` + +After: +```objc +... +/** + * @param value - value + * @param imageType - imageType + * @param isTemplate - @(isTemplate) + * @return A SDLImage object + */ +- (instancetype)initWithValue:(NSString *)value imageType:(SDLImageType)imageType isTemplate:(BOOL)isTemplate; +... +/** + * Required, BOOL + */ +@property (strong, nonatomic) NSNumber *isTemplate; +... +``` + +### "since" + +```json +{ + "structs": { + "AppServiceManifest": { + "rpcSpecVersion": { + "since": "5.0.0" + } + } + } +} +``` + +Before: +```objc +... +/** + * This is the max RPC Spec version the app service understands. This is important during the RPC passthrough + * functionality. If not included, it is assumed the max version of the module is acceptable. + * + * Optional, SDLSyncMsgVersion * + */ +@property (nullable, strong, nonatomic) SDLSyncMsgVersion *rpcSpecVersion; +... +``` + +After: +```objc +... +/** + * This is the max RPC Spec version the app service understands. This is important during the RPC passthrough + * functionality. If not included, it is assumed the max version of the module is acceptable. + * + * @since SDL 5.0.0 + * + * Optional, SDLSyncMsgVersion * + */ +@property (nullable, strong, nonatomic) SDLSyncMsgVersion *rpcSpecVersion; +... +``` + +### "of_class" + +```json +{ + "functions": { + "AddSubMenuRequest": { + "menuIcon": { + "of_class": "[SDLImage class]" + } + } + } +} +``` + +Before: +```objc +... +- (nullable SDLImage *)menuIcon { + return [self.parameters sdl_objectForName:SDLRPCParameterNameMenuIcon ofClass:SDLImage.class error:nil]; +} +... +``` + +After: +```objc +... +- (nullable SDLImage *)menuIcon { + return [self.parameters sdl_objectForName:SDLRPCParameterNameMenuIcon ofClass:[SDLImage class] error:nil]; +} +... +``` + + +### "type_native" + +```json +{ + "structs": { + "LightState": { + "density": { + "type_native": "double" + } + } + } +} +``` + +Before: +```objc +... +- (instancetype)initWithId:(SDLLightName)id status:(SDLLightStatus)status density:(float)density color:(nullable SDLRGBColor *)color; +... +``` + +After: +```objc +... +- (instancetype)initWithId:(SDLLightName)id status:(SDLLightStatus)status density:(double)density color:(nullable SDLRGBColor *)color; +... +``` + +### "type_sdl" + +```json +{ + "functions": { + "RegisterAppInterfaceRequest": { + "appHMIType": { + "type_sdl": "NSArray *" + } + } + } +} +``` + +sdl_ios/SmartDeviceLink/SDLRegisterAppInterface.h + +Before: +```objc +... +@property (nullable, strong, nonatomic) NSArray *appHMIType; +... +``` + +After: +```objc +... +@property (nullable, strong, nonatomic) NSArray *appHMIType; +... +``` + +### "modifier" + +```json +{ + "structs": { + "LocationDetails": { + "locationName": { + "modifier": "copy" + } + } + } +} +``` + +Before: +```objc +... +@property (nullable, strong, nonatomic) NSString *locationName; +... +``` + +After: +```objc +... +@property (nullable, copy, nonatomic) NSString *locationName; +... +``` + +### "for_name" + +```json +{ + "structs": { + "VehicleDataResult": { + "oemCustomDataType": { + "for_name": "enum" + } + } + } +} +``` + +Before: +```objc +... +- (nullable NSString *)oemCustomDataType { + return [self.store sdl_objectForName:SDLRPCParameterNameOemCustomDataType ofClass:NSString.class error:nil]; +} +... +``` + +After: +```objc +... +- (nullable NSString *)oemCustomDataType { + return [self.store sdl_enumForName:SDLRPCParameterNameOemCustomDataType ofClass:NSString.class error:nil]; +} +... +``` + + +### "description" + +```json +{ + "functions": { + "RegisterAppInterfaceResponse": { + "syncMsgVersion": { + "description": "Specifies the negotiated version number of the SmartDeviceLink protocol that is to be supported by the mobile application." + } + } + } +} +``` + +Before: +```objc +... +/** + * See SyncMsgVersion + */ +@property (nullable, strong, nonatomic) SDLSyncMsgVersion *syncMsgVersion; +... +``` + +After: +```objc +... +/** + * Specifies the negotiated version number of the SmartDeviceLink protocol that is to be supported by the mobile + * application. + */ +@property (nullable, strong, nonatomic) SDLSyncMsgVersion *syncMsgVersion; +... +``` From 20bbb94b19a36723576ed17e070c94579e46f9c8 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Thu, 27 Feb 2020 10:51:10 +0100 Subject: [PATCH 03/28] remove all custom mappings --- generator/mapping.json | 3 -- generator/templates/enums/FunctionID.h | 29 -------------- generator/templates/enums/FunctionID.m | 52 -------------------------- 3 files changed, 84 deletions(-) delete mode 100644 generator/templates/enums/FunctionID.h delete mode 100644 generator/templates/enums/FunctionID.m diff --git a/generator/mapping.json b/generator/mapping.json index e90187f04..60fe362be 100644 --- a/generator/mapping.json +++ b/generator/mapping.json @@ -1,8 +1,5 @@ { "enums": { - "FunctionID": { - "template": true - } }, "structs": { }, diff --git a/generator/templates/enums/FunctionID.h b/generator/templates/enums/FunctionID.h deleted file mode 100644 index 92e36ac29..000000000 --- a/generator/templates/enums/FunctionID.h +++ /dev/null @@ -1,29 +0,0 @@ -{% include 'copyright.txt' %} -// {{name}}.h - -#import -#import "NSNumber+NumberType.h" -#import "SDLRPCFunctionNames.h" - -NS_ASSUME_NONNULL_BEGIN - -/// A function ID for an SDL RPC -@interface {{name}} : NSObject - -/// The shared object for pulling function id information -+ (instancetype)sharedInstance; - -/// Gets the function name for a given SDL RPC function ID -/// -/// @param functionID A function ID -/// @returns An SDLRPCFunctionName -- (nullable SDLRPCFunctionName)functionNameForId:(UInt32)functionID; - -/// Gets the function ID for a given SDL RPC function name -/// -/// @param functionName The RPC function name -- (nullable NSNumber *)functionIdForName:(SDLRPCFunctionName)functionName; - -@end - -NS_ASSUME_NONNULL_END diff --git a/generator/templates/enums/FunctionID.m b/generator/templates/enums/FunctionID.m deleted file mode 100644 index 92ddacafc..000000000 --- a/generator/templates/enums/FunctionID.m +++ /dev/null @@ -1,52 +0,0 @@ -{% include 'copyright.txt' %} -// {{name}}.m - -#import "{{name}}.h" - -#import "NSMutableDictionary+Store.h" -#import "SDLRPCFunctionNames.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface {{name}} () - -@property (nonatomic, strong, nonnull) NSDictionary* functionIds; - -@end - -@implementation {{name}} - -+ (instancetype)sharedInstance { - static {{name}}* functionId = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - functionId = [[{{name}} alloc] init]; - }); - return functionId; -} - -- (instancetype)init { - self = [super init]; - if (!self) { - return nil; - } - - self.functionIds = @{ - {%- for param in params %} - @{{param.value}}: SDLRPCFunctionName{{param.name}}{{ ',' if not loop.last }} - {%- endfor %} - }; - return self; -} - -- (nullable SDLRPCFunctionName)functionNameForId:(UInt32)functionID { - return self.functionIds[@(functionID)]; -} - -- (nullable NSNumber *)functionIdForName:(SDLRPCFunctionName)functionName { - return [[self.functionIds allKeysForObject:functionName] firstObject]; -} - -@end - -NS_ASSUME_NONNULL_END From 7ca64540fbda90d984778f096bd936f84c1f155b Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Thu, 27 Feb 2020 12:36:33 +0100 Subject: [PATCH 04/28] remove all custom mappings --- generator/generator.py | 20 +- generator/mapping.json | 8 - generator/test/test_enums.py | 2 +- generator/test/test_functions.py | 2 +- generator/test/test_structs.py | 2 +- generator/transformers/common_producer.py | 223 +------------------ generator/transformers/enums_producer.py | 5 +- generator/transformers/functions_producer.py | 37 +-- generator/transformers/structs_producer.py | 5 +- 9 files changed, 16 insertions(+), 288 deletions(-) delete mode 100644 generator/mapping.json diff --git a/generator/generator.py b/generator/generator.py index 1b2a06aa4..9200652f0 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -245,7 +245,7 @@ def versions_compatibility_validating(self): self.logger.info('Parser type: %s, version %s,\tGenerator version %s', basename(getfile(Parser().__class__)), parser_origin, generator_origin) - async def get_paths(self, file_name=ROOT.joinpath('paths.ini')): + def get_paths(self, file_name=ROOT.joinpath('paths.ini')): """ :param file_name: path to file with container :return: namedtuple with container to key elements @@ -430,15 +430,6 @@ async def parser(self, source_xml, source_xsd): self.logger.error(error1) sys.exit(1) - async def get_all_async(self, source_xml, source_xsd): - """ - - :param source_xml: - :param source_xsd: - :return: - """ - return await asyncio.gather(self.parser(source_xml, source_xsd), self.get_paths(), self.get_mappings()) - def main(self): """ Entry point for parser and generator @@ -449,20 +440,21 @@ def main(self): self.versions_compatibility_validating() self.output_directory = args.output_directory - interface, paths, mappings = self.loop.run_until_complete(self.get_all_async(args.source_xml, args.source_xsd)) + interface = self.loop.run_until_complete(self.parser(args.source_xml, args.source_xsd)) + paths = self.get_paths() self.env = [args.templates_directory] + [join(args.templates_directory, k) for k in vars(interface).keys()] filtered, names = self.filter_pattern(interface, args.regex_pattern) tasks = [] - functions_transformer = FunctionsProducer(paths, names, mappings) + functions_transformer = FunctionsProducer(paths, names) if args.enums and filtered.enums: tasks.append(self.process_main(args.skip, args.overwrite, filtered.enums, - EnumsProducer(paths.enum_class, mappings))) + EnumsProducer(paths.enum_class))) if args.structs and filtered.structs: tasks.append(self.process_main(args.skip, args.overwrite, filtered.structs, - StructsProducer(paths.struct_class, names, mappings))) + StructsProducer(paths.struct_class, names))) if args.functions and filtered.functions: tasks.append(self.process_main(args.skip, args.overwrite, filtered.functions, functions_transformer)) tasks.append(self.process_function_name(args.skip, args.overwrite, interface.functions, diff --git a/generator/mapping.json b/generator/mapping.json deleted file mode 100644 index 60fe362be..000000000 --- a/generator/mapping.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "enums": { - }, - "structs": { - }, - "functions": { - } -} \ No newline at end of file diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py index 6efa64f15..d18a6c2ac 100644 --- a/generator/test/test_enums.py +++ b/generator/test/test_enums.py @@ -10,7 +10,7 @@ class TestEnumsProducer(TestCase): def setUp(self): self.maxDiff = None - self.producer = EnumsProducer('SDLEnum', defaultdict(dict)) + self.producer = EnumsProducer('SDLEnum') def test_FunctionID(self): elements = OrderedDict() diff --git a/generator/test/test_functions.py b/generator/test/test_functions.py index fdc6ee9d3..8b9867bc6 100644 --- a/generator/test/test_functions.py +++ b/generator/test/test_functions.py @@ -27,7 +27,7 @@ def setUp(self): parameter_names='SDLRPCParameterNames') names = ('FileType', 'Language', 'SyncMsgVersion', 'TemplateColorScheme', 'TTSChunk', 'Choice') - self.producer = FunctionsProducer(paths, names, defaultdict(dict)) + self.producer = FunctionsProducer(paths, names) def test_process_function_name(self): functions = { diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py index a1ee10f46..9fc65b476 100644 --- a/generator/test/test_structs.py +++ b/generator/test/test_structs.py @@ -12,7 +12,7 @@ class TestStructsProducer(TestCase): def setUp(self): self.maxDiff = None - self.producer = StructsProducer('SDLRPCStruct', ['Image'], defaultdict(dict)) + self.producer = StructsProducer('SDLRPCStruct', ['Image']) def test_Version(self): version = self.producer.get_version diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index 9ac9f5084..8fd48ec49 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -7,7 +7,6 @@ import textwrap from abc import ABC from collections import OrderedDict, namedtuple -from pprint import pformat from model.array import Array from model.boolean import Boolean @@ -27,10 +26,9 @@ class InterfaceProducerCommon(ABC): version = '1.0.0' - def __init__(self, container_name, mapping, names=()): + def __init__(self, container_name, names=()): self.logger = logging.getLogger(self.__class__.__name__) self.container_name = container_name - self.mapping = mapping self.names = names self.param_named = namedtuple('param_named', 'origin constructor_argument constructor_prefix deprecated mandatory since ' @@ -68,8 +66,6 @@ def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: if isinstance(item, (Struct, Function)): self.extract_imports(param, render['imports']) - self.custom_mapping(render) - if 'constructors' not in render and isinstance(item, (Struct, Function)): designated_initializer = render['designated_initializer'] if 'designated_initializer' in render else False render['constructors'] = self.extract_constructors(render['params'], designated_initializer) @@ -317,220 +313,3 @@ def extract_param(self, param: Param): data.update(self.extract_type(param)) data.update(self.param_origin_change(param.name)) return self.param_named(**data) - - @staticmethod - def minimize_last_id(name): - """ - - :param name: - :return: - """ - if name.upper().endswith('ID'): - return name[:-1] + name[-1:].lower() - return name - - @staticmethod - def remove_asterisk(data: dict): - """ - - :param data: - :return: - """ - for key, value in data.copy().items(): - if key.startswith('type') and value.endswith('*'): - if value.startswith('NSArray') and value.endswith('*> *'): - data[key] = value[:-5] + '> *' - else: - data[key] = value[:-1] - - @staticmethod - def add_asterisk(data: dict): - """ - - :param data: - :return: - """ - for key, value in data.copy().items(): - if key.startswith('type') and not value.endswith('*'): - data[key] = value.strip() + ' *' - - def evaluate_param(self, data, name): - """ - - :param name: - :param data: - :return: - """ - param_name = data['origin'] if 'origin' in data else '' - redundant = list(set(data.keys()) - set(self.param_named._fields)) - if redundant: - self.logger.error('%s/%s, redundant attributes (%s)', name, param_name, redundant) - return False - missed = list(set(self.param_named._fields) - set(data.keys())) - if missed: - self.logger.error('%s/%s, missed attributes (%s)', name, param_name, missed) - return False - return True - - def custom_mapping(self, render): - """ - - :param render: - :return: - """ - key = render['origin'] - if 'extends_class' in render: - key += render['extends_class'].replace('SDLRPC', '') - if key in self.mapping: - custom = self.mapping[key].copy() - elif render['origin'] in self.mapping: - custom = self.mapping[render['origin']].copy() - else: - return - self.logger.debug('%s fount in mapping', render['origin']) - - if 'params_title' in custom and custom['params_title'] is False: - for name, custom_data in render['params'].items(): - if re.match(r'^[A-Z\d]+$', custom_data.origin): - render['params'][name] = render['params'][name]._replace(name=custom_data.origin) - del custom['params_title'] - - if 'remove_asterisk' in custom: - for name, custom_data in render['params'].items(): - data = custom_data._asdict() - self.remove_asterisk(data) - render['params'][name] = custom_data._replace(**data) - del custom['remove_asterisk'] - - if 'description' in custom: - render['description'] = self.extract_description(custom['description']) - del custom['description'] - - if '-params' in custom: - for name in custom['-params']: - if name in render['params']: - imp = render['params'][name].of_class.replace('.class', '') - if imp in render['imports']['.m']: - render['imports']['.m'].remove(imp) - for kind in ('struct', 'enum'): - if imp in render['imports']['.h'][kind]: - render['imports']['.h'][kind].remove(imp) - del render['params'][name] - del custom['-params'] - - if 'minimize_last_id' in custom: - for name, custom_data in render['params'].items(): - if name.upper().endswith('ID'): - render['params'][name] = custom_data._replace( - constructor_argument=self.minimize_last_id(custom_data.constructor_argument), - constructor_prefix=self.minimize_last_id(custom_data.constructor_prefix), - method_suffix=self.minimize_last_id(custom_data.method_suffix)) - del custom['minimize_last_id'] - - if 'sort_params' in custom: - render['params'] = OrderedDict(sorted(render['params'].items())) - del custom['sort_params'] - - if 'template' in custom: - if isinstance(custom['template'], bool) and custom['template']: - render['template'] = render['name'][3:] - else: - render['template'] = custom['template'] - del custom['template'] - - if 'maximize_method' in custom: - if isinstance(custom['maximize_method'], str): - for name, custom_data in render['params'].items(): - tmp = re.findall(r'^([a-z]+)(\w*)$', self.minimize_first(custom_data.method_suffix)).pop() - render['params'][name] = custom_data._replace(method_suffix=tmp[0].upper() + tmp[1]) - elif isinstance(custom['maximize_method'], list): - for name in custom['maximize_method']: - if name in render['params']: - custom_data = render['params'][name] - tmp = re.findall(r'^([a-z]+)(\w*)$', self.minimize_first(custom_data.method_suffix)).pop() - render['params'][name] = custom_data._replace(method_suffix=tmp[0].upper() + tmp[1]) - del custom['maximize_method'] - - for key in ('modifier', 'mandatory'): - if key in custom: - for name, custom_data in render['params'].items(): - render['params'][name] = custom_data._replace(**{key: custom[key]}) - del custom[key] - - for key in ('name', 'designated_initializer', 'deprecated', 'NS_ENUM', 'NS_SWIFT_NAME', 'add_typedef'): - if key in custom and isinstance(custom[key], str): - render[key] = custom[key] - del custom[key] - - for name, custom_data in custom.copy().items(): - self.logger.info('applying manual mapping for %s/%s\t%s', render['name'], name, pformat(custom_data)) - if name in render['params']: - if isinstance(custom_data, str): - render['params'][name] = render['params'][name]._replace(name=custom_data) - elif isinstance(custom_data, dict): - data = self.custom_param_update(render['params'][name]._asdict(), custom_data, render['imports']) - if self.evaluate_param(data, render['origin']): - render['params'][name] = self.param_named(**data) - del custom[name] - elif name not in ['sort_constructor']: - if 'description' in custom[name]: - custom[name]['description'] = self.extract_description(custom[name]['description']) - custom[name]['origin'] = name - for key, value in custom[name].copy().items(): - if key.startswith('type') and not value.endswith('*'): - custom[name][key] = value.strip() + ' ' - if self.evaluate_param(custom[name], render['origin']): - render['params'][name] = self.param_named(**custom[name]) - render['params'].move_to_end(name, last=False) - else: - self.logger.warning('For %s provided undefined mapping for "%s": %s, which will be skipped', - render['name'], name, pformat(custom_data)) - del custom[name] - - if 'sort_constructor' in custom: - sorted_params = OrderedDict(sorted(render['params'].items())) - render['constructors'] = self.extract_constructors(sorted_params) - del custom['sort_constructor'] - - @staticmethod - def custom_param_update(data, custom_data, imports) -> dict: - """ - - :param data: - :param custom_data: - :param imports: - :return: - """ - if 'description' in custom_data: - custom_data['description'] = InterfaceProducerCommon.extract_description(custom_data['description']) - if 'minimize_last_id' in custom_data: - data['constructor_argument'] = InterfaceProducerCommon.minimize_last_id(data['constructor_argument']) - data['constructor_prefix'] = InterfaceProducerCommon.minimize_last_id(data['constructor_prefix']) - data['method_suffix'] = InterfaceProducerCommon.minimize_last_id(data['method_suffix']) - if 'maximize_method' in custom_data: - tmp = re.findall(r'^([a-z]+)(\w*)$', InterfaceProducerCommon.minimize_first(data['method_suffix'])).pop() - data['method_suffix'] = tmp[0].upper() + tmp[1] - if 'origin' in custom_data: - data.update(InterfaceProducerCommon.param_origin_change(custom_data['origin'])) - if 'type' in custom_data: - new_type = re.sub(r'NSArray|[\s<>*]', '', custom_data['type']) - data['type_native'] = data['type_sdl'] = re.sub(r'[\s*]', '', custom_data['type']) + ' *' - data['method_suffix'] = new_type - if data['of_class']: - data['of_class'] = new_type + '.class' - if new_type.lower() in map(str.lower, imports['.m']): - imports['.m'] = set(i for i in imports['.m'] if i.lower() != new_type.lower()) - imports['.m'].add(new_type) - for kind in ('enum', 'struct'): - if new_type.lower() in map(str.lower, imports['.h'][kind]): - imports['.h'][kind] = set(i for i in imports['.h'][kind] if i.lower() != new_type.lower()) - imports['.h'][kind].add(new_type) - if 'remove_asterisk' in custom_data: - InterfaceProducerCommon.remove_asterisk(data) - if 'add_asterisk' in custom_data: - InterfaceProducerCommon.add_asterisk(data) - for key, value in custom_data.copy().items(): - if key.startswith('type') and not value.endswith('*'): - custom_data[key] = value.strip() + ' ' - data.update(custom_data) - return data diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py index 9dc8b7834..92b72b283 100644 --- a/generator/transformers/enums_producer.py +++ b/generator/transformers/enums_producer.py @@ -15,10 +15,9 @@ class EnumsProducer(InterfaceProducerCommon): Enums transformer """ - def __init__(self, enum_class, mapping=None): + def __init__(self, enum_class): super(EnumsProducer, self).__init__( - container_name='elements', - mapping=mapping.get('enums', {})) + container_name='elements') self.enum_class = enum_class self.logger = logging.getLogger(self.__class__.__name__) self.param_named = namedtuple('param_named', 'origin description name since value') diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py index d227b62ea..5cb562047 100644 --- a/generator/transformers/functions_producer.py +++ b/generator/transformers/functions_producer.py @@ -4,7 +4,6 @@ import logging from collections import namedtuple, OrderedDict -from pprint import pformat from model.function import Function from transformers.common_producer import InterfaceProducerCommon @@ -15,11 +14,10 @@ class FunctionsProducer(InterfaceProducerCommon): Functions transformer """ - def __init__(self, paths, names, mapping=None): + def __init__(self, paths, names): super(FunctionsProducer, self).__init__( container_name='params', - names=names, - mapping=mapping.get('functions', {})) + names=names) self.request_class = paths.request_class self.response_class = paths.response_class self.notification_class = paths.notification_class @@ -76,8 +74,6 @@ def get_function_names(self, items: dict) -> dict: 'since': item.since} render[item.name] = self.common_names(**tmp) - self.custom_mapping_names(render, self.function_names) - return {'params': sorted(render.values(), key=lambda a: a.name)} def get_simple_params(self, functions: dict, structs: dict) -> dict: @@ -107,33 +103,4 @@ def evaluate(element): for param in struct.members.values(): render.update(evaluate(param)) - self.custom_mapping_names(render, self.parameter_names) - return {'params': sorted(render.values(), key=lambda a: a.name)} - - def custom_mapping_names(self, render, file_name): - """ - - :param render: - :param file_name: - :return: - """ - if file_name in self.mapping: - self.logger.debug('applying manual mapping for %s\n%s', file_name, pformat(self.mapping[file_name])) - for name, item in self.mapping[file_name].items(): - if isinstance(item, dict) and name in render: - render[name] = render[name]._replace(**item) - elif isinstance(item, list): - for value in item: - data = OrderedDict().fromkeys(self.common_names._fields) - data.update(value) - render[value['name']] = self.common_names(**data) - elif name in render: - render[name] = render[name]._replace(name=item) - elif isinstance(item, dict): - data = OrderedDict().fromkeys(self.common_names._fields) - data.update(item) - render[name] = self.common_names(**data) - else: - render[name] = self.common_names(item, name, '', '') - self.logger.warning('Into %s added name="%s", origin="%s"', self.function_names, item, name) diff --git a/generator/transformers/structs_producer.py b/generator/transformers/structs_producer.py index 8f48eb8bd..86a596095 100644 --- a/generator/transformers/structs_producer.py +++ b/generator/transformers/structs_producer.py @@ -14,11 +14,10 @@ class StructsProducer(InterfaceProducerCommon): Structs transformer """ - def __init__(self, struct_class, enum_names, mapping=None): + def __init__(self, struct_class, enum_names): super(StructsProducer, self).__init__( container_name='members', - names=enum_names, - mapping=mapping.get('structs', {})) + names=enum_names) self.struct_class = struct_class self.logger = logging.getLogger(self.__class__.__name__) From 6276035234e13b585372bc52b79362649f02b014 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Thu, 27 Feb 2020 13:23:40 +0100 Subject: [PATCH 05/28] update doc --- generator/README.md | 1013 ------------------------------------------- 1 file changed, 1013 deletions(-) diff --git a/generator/README.md b/generator/README.md index 60155f4c6..1804079bc 100644 --- a/generator/README.md +++ b/generator/README.md @@ -1144,1016 +1144,3 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` - -# Custom mapping - -There are cases named `edge cases` when it is not possible to get the required info from XML or some manual additions are required in generated classes. For that purpose the generator includes the custom mapping file `mapping.json` that allows to add required customizations. - -## Structure of `mapping.json` - -The customization script contains the JSON object. Below is the schema: - -Enums: -```json5 -{ - ["enums"]: { - [enum_name]: { - "template": [true|custom_template_name], - "name": [custom_enum_namee], - "description": [custom_description_string], - "deprecated": [custom_deprecated_description_string], - "-params": [element_to_delete, param_to_delete, ...], - "params_title": false - [element_name]: { - "origin": [custom_element_name], - "description": [custom_description_string], - "since": [custom_since_version_string], - "value": [custom_value] - } - } - } -} -``` - -Structs and Functions: -```json5 -{ - ["structs"|"functions"]: { - [struct_name|function_name]: { - "template": [true|custom_template_name], - "name": [custom_struct_name|custom_function_name], - "description": [custom_description_string], - "deprecated": [custom_deprecated_description_string], - "sort_constructor": true, - "-params": [element_to_delete, param_to_delete, ...], - [param_name|member_name]: { - "origin": [custom_param_name|custom_member_name], - "constructor_argument": [custom_constructor_argument_name_string], - "constructor_prefix": [custom_constructor_parameter_name_string], - "method_suffix": [custom_forName:SDLRPCParameterName_method_suffix_name_string], - "deprecated": [custom_deprecated_description_string], - "mandatory": [true|false], - "since": [custom_since_version_string], - "of_class": [custom_"ofClass:{{of_class}}"_string], - "type_native": [custom_type_used_in_constructor_string], - "type_sdl": [custom_type_used_in_methods_string], - "modifier": [assign|copy|strong|weak], - "for_name": [custom_"_sdl_{{for_name}}"_name_string], - "description": [custom_description_string], - "constructor_argument_override": [custom_constructor_argument_string], - } - } - } -} -``` - -Root keys in the structure are `"enums"`, `"structs"` and `"functions"`. -On the next level expected `name`, `template`, `description`, `deprecated`, `sort_constructor`, `-params`, `params_title` or corresponding name of required ``, ``, ``. -Then the next level, is set of properties which present in each corresponding structure. -In case when corresponding structure exist not required to list all properties. - -The mapping object provide the possibility to create brand new ``, `` or ``, in this case all corresponding properties should be provided. - -## "template" - -It is possible to create custom template for any corresponding structure (``, ``, ``). - -As template engine used jinja, which has own language, documentation - https://jinja.palletsprojects.com/en/2.11.x/ -like it was done for `sdl_ios/generator/templates/enums/FunctionID.*`, otherwise you can extend existing template and add required functionality. - -There are following block's in default template: -* {% block imports %} -* {% block constructors %} -* {% block methods %} - -You can extend it by adding your custom code and call `{{super()}}` to add code from parent template, example: -```jinja -{%- block imports %} -yout code -{{super()}} -{%- endblock -%} -``` - -To enable custom template feature add following into `sdl_ios/generator/mapping.json`: -```json -{ - "functions": { - "PutFile": { - "template": true - } - } -} -``` - -Create custom template - `sdl_ios/generator/templates/functions/PutFile.h`: -```jinja -{% extends 'functions/template.h' %} -{%- block imports %} -#import -{{super()}} -{%- endblock -%} -{%- block constructors -%} -{{super()}} -- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData; -{% endblock -%} -{%- block methods %} -@property (nullable, copy, nonatomic) SDLRPCCommandNotificationHandler handler; -{{super()}} -{%- endblock -%} -``` - -As a result the generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLPutFile.h`: -```objc -... -#import -... -- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData; -... -@property (nullable, copy, nonatomic) SDLRPCCommandNotificationHandler handler; -... -``` - -Create custom template - `sdl_ios/generator/templates/functions/PutFile.m`: -```jinja -{% extends 'functions/template.h' %} -{%- block imports -%} -{{super()}} - -static NSString *const SDLBundleShortVersionStringKey = @"CFBundleShortVersionString"; -static NSString *const SDLBundleAppNameKey = @"CFBundleName"; -{%- endblock -%} -{%- block constructors -%} -{{super()}} -- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData { - - self = [self initWithFileName:fileName fileType:fileType persistentFile:persistentFile systemFile:systemFile offset:offset length:length crc:[self.class sdl_getCRC32ChecksumForBulkData:bulkData]]; - if (!self) { - return nil; - } - - self.bulkData = bulkData; - - return self; -} - -#pragma mark - Getters and Setters -{% endblock -%} -{%- block methods %} -#pragma mark - Getters / Setters -{{super()}} --(id)copyWithZone:(nullable NSZone *)zone { - SDLAddCommand *newCommand = [super copyWithZone:zone]; - newCommand->_handler = self.handler; - - return newCommand; -} -{% endblock -%} -``` - -As result generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLPutFile.m`: -```objc -... -static NSString *const SDLBundleShortVersionStringKey = @"CFBundleShortVersionString"; -static NSString *const SDLBundleAppNameKey = @"CFBundleName"; -... -- (instancetype)initWithFileName:(NSString *)fileName fileType:(SDLFileType)fileType persistentFile:(BOOL)persistentFile systemFile:(BOOL)systemFile offset:(UInt32)offset length:(UInt32)length bulkData:(NSData *)bulkData { - - self = [self initWithFileName:fileName fileType:fileType persistentFile:persistentFile systemFile:systemFile offset:offset length:length crc:[self.class sdl_getCRC32ChecksumForBulkData:bulkData]]; - if (!self) { - return nil; - } - - self.bulkData = bulkData; - - return self; -} - -#pragma mark - Getters and Setters -... --(id)copyWithZone:(nullable NSZone *)zone { - SDLAddCommand *newCommand = [super copyWithZone:zone]; - newCommand->_handler = self.handler; - - return newCommand; -} -... -``` - -## "name" - -Change name of original structure. - -Notice: Name of class and name of file is the the same, so overriding of "name" property aso change the name of target file. - -Example: -```json -{ - "enums": { - "HmiZoneCapabilities": { - "name": "SDLHMIZoneCapabilities" - } - } -} -``` - -`sdl_ios/SmartDeviceLink/SDLHMIZoneCapabilities.m`: -```objc -... -#import "SDLHMIZoneCapabilities.h" -... -SDLHMIZoneCapabilities const SDLHMIZoneCapabilitiesFront = @"FRONT"; -... -``` - -`sdl_ios/SmartDeviceLink/SDLHMIZoneCapabilities.h`: -```objc -... -typedef SDLEnum SDLHMIZoneCapabilities SDL_SWIFT_ENUM; -... -extern SDLHMIZoneCapabilities const SDLHMIZoneCapabilitiesFront; -... -``` - -## "description" - -```json -{ - "functions": { - "RegisterAppInterfaceResponse": { - "description": "long custom description" - } - } -} -``` - -As a result the generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLRegisterAppInterfaceResponse.h`: -```objc -... -/** - * long custom description - */ -@interface SDLRegisterAppInterfaceResponse : SDLRPCResponse -... -``` - -## "deprecated" - -If particular structure marked as deprecated is source xml, e.g.: -```xml - -``` - -By default the generated file will look like the following: -```objc -... -/** - * @deprecated - */ -typedef SDLEnum SDLSupportedSeat SDL_SWIFT_ENUM __deprecated; -... -``` - -But you are able to provide description for deprecated structure: -```json -{ - "enums": { - "SupportedSeat": { - "deprecated": "custom deprecated description" - } - } -} -``` -As a result the generated file will look like the following - `sdl_ios/SmartDeviceLink/SDLSupportedSeat.h`: -```objc -... -/** - * @deprecated - */ -typedef SDLEnum SDLSupportedSeat SDL_SWIFT_ENUM __deprecated_msg("custom deprecated description"); -... -``` - -In case if particular structure wasn't marked as deprecated is source xml and you added "deprecated" for it into "mapping.json", appropriate structure will be marked as deprecated - -## "sort_constructor" - -All "elements|members|params" from particular structure will be transformed into target code in same sequence in which it present in source xml. -By default the appropriate initiator (constructor) will be created from all "elements|members|params" with same sequence in which it present in source xml. - -To sort all "elements|members|params" by alphabetical order and then creating appropriate initiator (constructor) use following mapping parameter: - -```json -{ - "functions": { - "RegisterAppInterfaceResponse": { - "sort_constructor": true - } - } -} -``` - -Before: -```objc -... -/** - * @param syncMsgVersion - syncMsgVersion - * @param language - language - * @param hmiDisplayLanguage - hmiDisplayLanguage - * @param displayCapabilities - displayCapabilities - * @param buttonCapabilities - buttonCapabilities - * @param softButtonCapabilities - softButtonCapabilities - * @param presetBankCapabilities - presetBankCapabilities - * @param hmiZoneCapabilities - hmiZoneCapabilities - * @param speechCapabilities - speechCapabilities - * @param prerecordedSpeech - prerecordedSpeech - * @param vrCapabilities - vrCapabilities - * @param audioPassThruCapabilities - audioPassThruCapabilities - * @param pcmStreamCapabilities - pcmStreamCapabilities - * @param vehicleType - vehicleType - * @param supportedDiagModes - supportedDiagModes - * @param hmiCapabilities - hmiCapabilities - * @param sdlVersion - sdlVersion - * @param systemSoftwareVersion - systemSoftwareVersion - * @param iconResumed - @(iconResumed) - * @return A SDLRegisterAppInterfaceResponse object - */ -- (instancetype)initWithSyncMsgVersion:(nullable SDLSyncMsgVersion *)syncMsgVersion language:(nullable SDLLanguage)language hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage displayCapabilities:(nullable SDLDisplayCapabilities *)displayCapabilities buttonCapabilities:(nullable NSArray *)buttonCapabilities softButtonCapabilities:(nullable NSArray *)softButtonCapabilities presetBankCapabilities:(nullable SDLPresetBankCapabilities *)presetBankCapabilities hmiZoneCapabilities:(nullable NSArray *)hmiZoneCapabilities speechCapabilities:(nullable NSArray *)speechCapabilities prerecordedSpeech:(nullable NSArray *)prerecordedSpeech vrCapabilities:(nullable NSArray *)vrCapabilities audioPassThruCapabilities:(nullable NSArray *)audioPassThruCapabilities pcmStreamCapabilities:(nullable SDLAudioPassThruCapabilities *)pcmStreamCapabilities vehicleType:(nullable SDLVehicleType *)vehicleType supportedDiagModes:(nullable NSArray *> *)supportedDiagModes hmiCapabilities:(nullable SDLHMICapabilities *)hmiCapabilities sdlVersion:(nullable NSString *)sdlVersion systemSoftwareVersion:(nullable NSString *)systemSoftwareVersion iconResumed:(BOOL)iconResumed; -... -``` - -After: -```objc -/** - * @param audioPassThruCapabilities - audioPassThruCapabilities - * @param buttonCapabilities - buttonCapabilities - * @param displayCapabilities - displayCapabilities - * @param hmiCapabilities - hmiCapabilities - * @param hmiDisplayLanguage - hmiDisplayLanguage - * @param hmiZoneCapabilities - hmiZoneCapabilities - * @param iconResumed - @(iconResumed) - * @param language - language - * @param pcmStreamCapabilities - pcmStreamCapabilities - * @param prerecordedSpeech - prerecordedSpeech - * @param presetBankCapabilities - presetBankCapabilities - * @param sdlVersion - sdlVersion - * @param softButtonCapabilities - softButtonCapabilities - * @param speechCapabilities - speechCapabilities - * @param supportedDiagModes - supportedDiagModes - * @param syncMsgVersion - syncMsgVersion - * @param systemSoftwareVersion - systemSoftwareVersion - * @param vehicleType - vehicleType - * @param vrCapabilities - vrCapabilities - * @return A SDLRegisterAppInterfaceResponse object - */ -- (instancetype)initWithAudioPassThruCapabilities:(nullable NSArray *)audioPassThruCapabilities buttonCapabilities:(nullable NSArray *)buttonCapabilities displayCapabilities:(nullable SDLDisplayCapabilities *)displayCapabilities hmiCapabilities:(nullable SDLHMICapabilities *)hmiCapabilities hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage hmiZoneCapabilities:(nullable NSArray *)hmiZoneCapabilities iconResumed:(BOOL)iconResumed language:(nullable SDLLanguage)language pcmStreamCapabilities:(nullable SDLAudioPassThruCapabilities *)pcmStreamCapabilities prerecordedSpeech:(nullable NSArray *)prerecordedSpeech presetBankCapabilities:(nullable SDLPresetBankCapabilities *)presetBankCapabilities sdlVersion:(nullable NSString *)sdlVersion softButtonCapabilities:(nullable NSArray *)softButtonCapabilities speechCapabilities:(nullable NSArray *)speechCapabilities supportedDiagModes:(nullable NSArray *> *)supportedDiagModes syncMsgVersion:(nullable SDLSyncMsgVersion *)syncMsgVersion systemSoftwareVersion:(nullable NSString *)systemSoftwareVersion vehicleType:(nullable SDLVehicleType *)vehicleType vrCapabilities:(nullable NSArray *)vrCapabilities; -``` - -## "-params" - -If you need to remove some "elements|members|params", list them as follow: -```json -{ - "functions": { - "RegisterAppInterfaceResponse": { - "-params": [ - "syncMsgVersion" - ] - } - } -} -``` - -Before: -```objc -... -@class SDLSyncMsgVersion; -... -/** - * @param syncMsgVersion - syncMsgVersion - * @param language - language - * @param hmiDisplayLanguage - hmiDisplayLanguage - * @return A SDLRegisterAppInterfaceResponse object - */ -- (instancetype)initWithSyncMsgVersion:(nullable SDLSyncMsgVersion *)syncMsgVersion language:(nullable SDLLanguage)language hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage; - -@property (nullable, strong, nonatomic) SDLSyncMsgVersion *syncMsgVersion; -... -``` - -After: -```objc -... -/** - * @param language - language - * @param hmiDisplayLanguage - hmiDisplayLanguage - * @return A SDLRegisterAppInterfaceResponse object - */ -- (instancetype)initWithLanguage:(nullable SDLLanguage)language hmiDisplayLanguage:(nullable SDLLanguage)hmiDisplayLanguage; -... -``` - -As result generated listed "elements|members|params" will be removed from appropriate getters/setters, initiators (constructors) and imports. - -## "params_title" - -Used for Enums only. - -To avoid capitalizing elements (used named from original source XML) use following parameter: -```json -{ - "enums": { - "AudioType": { - "params_title": false - } - } -} -``` - -Before: -```objc -... -extern SDLAudioType const SDLAudioTypePcm; -... -``` - -After: -```objc -.. -extern SDLAudioType const SDLAudioTypePCM; -... -``` - -## Customization sub elements|params|members - -## `Enum` elements customization - -### "value" - -```json -{ - "enums": { - "FunctionID": { - "PerformInteractionID": { - "value": "0XFF" - } - } - } -} -``` - -Before: -```objc -... -@10: SDLRPCFunctionNamePerformInteraction, -... -``` - -After: -```objc -... -@0XFF: SDLRPCFunctionNamePerformInteraction, -... -``` - -## `Struct` members and `Function` params customization - -### "origin" - -changing of "origin" also will affect "constructor_argument", "constructor_prefix" and "method_suffix" - -```json -{ - "structs": { - "RdsData": { - "PS": { - "origin": "programService" - } - } - } -} -``` - -Before: -```objc -... -- (instancetype)initWithPS:(nullable NSString *)PS -... -@property (nullable, strong, nonatomic) NSString *PS; -... -``` - -After: -```objc -... -- (instancetype)initWithProgramService:(nullable NSString *)programService -... -@property (nullable, strong, nonatomic) NSString *programService; -... -``` - -### "constructor_argument" - -initiator (constructor) parameter name - -```json -{ - "structs": { - "KeyboardProperties": { - "limitedCharacterList": { - "constructor_argument": "example_limitedCharacterList" - } - } - } -} -``` - -After: - -sdl_ios/SmartDeviceLink/SDLKeyboardProperties.h -```objc -... -/** - * @param language - language - * @param example_limitedCharacterList - example_limitedCharacterList - * @return A SDLKeyboardProperties object - */ -- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)example_limitedCharacterList; -... -``` - -sdl_ios/SmartDeviceLink/SDLKeyboardProperties.m -```objc -... -- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)example_limitedCharacterList{ - self = [super init]; - if (!self) { - return nil; - } - self.language = language; - self.limitedCharacterList = example_limitedCharacterList; -... -``` - -### "constructor_argument_override" - -```json -{ - "structs": { - "KeyboardProperties": { - "limitedCharacterList": { - "constructor_argument_override": "[limitedCharacterList mutableCopy]" - } - } - } -} -``` - -After: - -sdl_ios/SmartDeviceLink/SDLKeyboardProperties.h -```objc -... -/** - * @param language - language - * @param limitedCharacterList - [limitedCharacterList mutableCopy] - * @return A SDLKeyboardProperties object - */ -- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)limitedCharacterList; -... -``` - -sdl_ios/SmartDeviceLink/SDLKeyboardProperties.m -```objc -... -- (instancetype)initWithLanguage:(nullable SDLLanguage)language limitedCharacterList:(nullable NSArray *)limitedCharacterList { - self = [super init]; - if (!self) { - return nil; - } - self.language = language; - self.limitedCharacterList = [limitedCharacterList mutableCopy]; -... -``` - -### "constructor_prefix" - -```json -{ - "structs": { - "ImageResolution": { - "resolutionWidth": { - "constructor_prefix": "Width" - } - } - } -} -``` - -Before: -```objc -... -/** - * @param resolutionWidth - @(resolutionWidth) - * @return A SDLImageResolution object - */ -- (instancetype)initWithResolutionWidth:(UInt16)resolutionWidth; -... -``` - -After: -```objc -... -/** - * @param width - @(resolutionWidth) - * @return A SDLImageResolution object - */ -- (instancetype)initWithWidth:(UInt16)resolutionWidth; -... -``` - -### "method_suffix" - -```json -{ - "structs": { - "TouchEvent": { - "c": { - "method_suffix": "Coordinate" - } - } - } -} -``` - -Before: -```objc -... -- (void)setC:(NSArray *)c { - [self.store sdl_setObject:c forName:SDLRPCParameterNameC]; -} - -- (NSArray *)c { - NSError *error = nil; - return [self.store sdl_objectsForName:SDLRPCParameterNameC ofClass:SDLTouchCoord.class error:&error]; -} -... -``` - -After: -```objc -... -- (void)setC:(NSArray *)c { - [self.store sdl_setObject:c forName:SDLRPCParameterNameCoordinate]; -} - -- (NSArray *)c { - NSError *error = nil; - return [self.store sdl_objectsForName:SDLRPCParameterNameCoordinate ofClass:SDLTouchCoord.class error:&error]; -} -... -``` - -### "deprecated" - -```json -{ - "structs": { - "KeyboardProperties": { - "autoCompleteText": { - "deprecated": false - } - } - } -} -``` -```objc -... -@property (nullable, strong, nonatomic) NSString *autoCompleteText; -... -``` -#### -```json -{ - "structs": { - "KeyboardProperties": { - "autoCompleteText": { - "deprecated": "long deprecation description" - } - } - } -} -``` -```objc -... -/** - * @deprecated - */ -@property (nullable, strong, nonatomic) NSString *autoCompleteText __deprecated_msg("long deprecation description"); -... -``` -#### -```json -{ - "structs": { - "KeyboardProperties": { - "autoCompleteText": { - "deprecated": true - } - } - } -} -``` -```objc -... -/** - * @deprecated - */ -@property (nullable, strong, nonatomic) NSString *autoCompleteText __deprecated; -... -``` - -### "mandatory" - -```json -{ - "structs": { - "Image": { - "isTemplate": { - "mandatory": true - } - } - } -} -``` - -Before: -```objc -... -/** - * @param value - value - * @param imageType - imageType - * @return A SDLImage object - */ -- (instancetype)initWithValue:(NSString *)value imageType:(SDLImageType)imageType; - -/** - * @param value - value - * @param imageType - imageType - * @param isTemplate - @(isTemplate) - * @return A SDLImage object - */ -- (instancetype)initWithValue:(NSString *)value imageType:(SDLImageType)imageType isTemplate:(BOOL)isTemplate; -... -/** - * Optional, BOOL - */ -@property (nullable, strong, nonatomic) NSNumber *isTemplate; -... -``` - -After: -```objc -... -/** - * @param value - value - * @param imageType - imageType - * @param isTemplate - @(isTemplate) - * @return A SDLImage object - */ -- (instancetype)initWithValue:(NSString *)value imageType:(SDLImageType)imageType isTemplate:(BOOL)isTemplate; -... -/** - * Required, BOOL - */ -@property (strong, nonatomic) NSNumber *isTemplate; -... -``` - -### "since" - -```json -{ - "structs": { - "AppServiceManifest": { - "rpcSpecVersion": { - "since": "5.0.0" - } - } - } -} -``` - -Before: -```objc -... -/** - * This is the max RPC Spec version the app service understands. This is important during the RPC passthrough - * functionality. If not included, it is assumed the max version of the module is acceptable. - * - * Optional, SDLSyncMsgVersion * - */ -@property (nullable, strong, nonatomic) SDLSyncMsgVersion *rpcSpecVersion; -... -``` - -After: -```objc -... -/** - * This is the max RPC Spec version the app service understands. This is important during the RPC passthrough - * functionality. If not included, it is assumed the max version of the module is acceptable. - * - * @since SDL 5.0.0 - * - * Optional, SDLSyncMsgVersion * - */ -@property (nullable, strong, nonatomic) SDLSyncMsgVersion *rpcSpecVersion; -... -``` - -### "of_class" - -```json -{ - "functions": { - "AddSubMenuRequest": { - "menuIcon": { - "of_class": "[SDLImage class]" - } - } - } -} -``` - -Before: -```objc -... -- (nullable SDLImage *)menuIcon { - return [self.parameters sdl_objectForName:SDLRPCParameterNameMenuIcon ofClass:SDLImage.class error:nil]; -} -... -``` - -After: -```objc -... -- (nullable SDLImage *)menuIcon { - return [self.parameters sdl_objectForName:SDLRPCParameterNameMenuIcon ofClass:[SDLImage class] error:nil]; -} -... -``` - - -### "type_native" - -```json -{ - "structs": { - "LightState": { - "density": { - "type_native": "double" - } - } - } -} -``` - -Before: -```objc -... -- (instancetype)initWithId:(SDLLightName)id status:(SDLLightStatus)status density:(float)density color:(nullable SDLRGBColor *)color; -... -``` - -After: -```objc -... -- (instancetype)initWithId:(SDLLightName)id status:(SDLLightStatus)status density:(double)density color:(nullable SDLRGBColor *)color; -... -``` - -### "type_sdl" - -```json -{ - "functions": { - "RegisterAppInterfaceRequest": { - "appHMIType": { - "type_sdl": "NSArray *" - } - } - } -} -``` - -sdl_ios/SmartDeviceLink/SDLRegisterAppInterface.h - -Before: -```objc -... -@property (nullable, strong, nonatomic) NSArray *appHMIType; -... -``` - -After: -```objc -... -@property (nullable, strong, nonatomic) NSArray *appHMIType; -... -``` - -### "modifier" - -```json -{ - "structs": { - "LocationDetails": { - "locationName": { - "modifier": "copy" - } - } - } -} -``` - -Before: -```objc -... -@property (nullable, strong, nonatomic) NSString *locationName; -... -``` - -After: -```objc -... -@property (nullable, copy, nonatomic) NSString *locationName; -... -``` - -### "for_name" - -```json -{ - "structs": { - "VehicleDataResult": { - "oemCustomDataType": { - "for_name": "enum" - } - } - } -} -``` - -Before: -```objc -... -- (nullable NSString *)oemCustomDataType { - return [self.store sdl_objectForName:SDLRPCParameterNameOemCustomDataType ofClass:NSString.class error:nil]; -} -... -``` - -After: -```objc -... -- (nullable NSString *)oemCustomDataType { - return [self.store sdl_enumForName:SDLRPCParameterNameOemCustomDataType ofClass:NSString.class error:nil]; -} -... -``` - - -### "description" - -```json -{ - "functions": { - "RegisterAppInterfaceResponse": { - "syncMsgVersion": { - "description": "Specifies the negotiated version number of the SmartDeviceLink protocol that is to be supported by the mobile application." - } - } - } -} -``` - -Before: -```objc -... -/** - * See SyncMsgVersion - */ -@property (nullable, strong, nonatomic) SDLSyncMsgVersion *syncMsgVersion; -... -``` - -After: -```objc -... -/** - * Specifies the negotiated version number of the SmartDeviceLink protocol that is to be supported by the mobile - * application. - */ -@property (nullable, strong, nonatomic) SDLSyncMsgVersion *syncMsgVersion; -... -``` From b2831fb0732b283e19b17eb6117dd74e0921a4df Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Mon, 2 Mar 2020 00:37:16 +0100 Subject: [PATCH 06/28] changing according to review comments --- generator/generator.py | 1 + generator/templates/base_struct_function.h | 50 ++------------------ generator/templates/description.jinja | 23 +++++++++ generator/templates/description_param.jinja | 20 ++++++++ generator/templates/enums/template.h | 41 +--------------- generator/templates/enums/template_numeric.h | 11 +++++ generator/transformers/common_producer.py | 4 ++ generator/transformers/enums_producer.py | 2 + 8 files changed, 66 insertions(+), 86 deletions(-) create mode 100644 generator/templates/description.jinja create mode 100644 generator/templates/description_param.jinja create mode 100644 generator/templates/enums/template_numeric.h diff --git a/generator/generator.py b/generator/generator.py index 9200652f0..d9c91ac61 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -373,6 +373,7 @@ async def process_common(self, skip, overwrite, file_with_suffix, data, template return if overwrite: self.logger.info('Overriding %s', file_with_suffix.name) + file_with_suffix.unlink() self.write_file(file_with_suffix, templates, data) else: while True: diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h index c8a99cfd1..c4ee1d142 100644 --- a/generator/templates/base_struct_function.h +++ b/generator/templates/base_struct_function.h @@ -10,30 +10,9 @@ @class {{import}}; {%- endfor %} {%- endblock %} -{%- if deprecated and deprecated is sameas true -%} -{%- set ending = ' __deprecated' -%} -{%- elif deprecated and deprecated is string -%} -{%- set ending = ' __deprecated_msg("{}")'.format(deprecated) -%} -{%- endif %} NS_ASSUME_NONNULL_BEGIN -{% if description or since %} -/** - {%- if description %} - {%- for d in description %} - * {{d}} - {%- endfor %}{% endif -%} - {%- if description and since %} - * - {%- endif %} - {%- if deprecated %} - * @deprecated - {%- endif %} - {%- if since %} - * @since SDL {{since}} - {%- endif %} - */ -{%- endif %} +{% include 'description.jinja' %} @interface {{name}} : {{extends_class}}{{ending}} {%- block constructors %} {% for c in constructors %} @@ -61,31 +40,8 @@ NS_ASSUME_NONNULL_BEGIN {%- endblock -%} {%- block methods %} {%- for param in params %} -/** - {%- if param.description %} - {%- for d in param.description %} - * {{d}} - {%- endfor %}{% endif -%} - {%- if param.description and param.since %} - * - {%- endif %} - {%- if param.deprecated %} - * @deprecated - {%- endif %} - {%- if param.since %} - * @since SDL {{param.since}} - {%- endif %} - {%- if param.description or param.since %} - * - {%- endif %} - * {{'Required, ' if param.mandatory else 'Optional, '}}{{param.type_native}} - */ -{%- if param.deprecated and param.deprecated is sameas true -%} -{%- set ending = ' __deprecated' -%} -{%- elif param.deprecated and param.deprecated is string -%} -{%- set ending = ' __deprecated_msg("{}")'.format(param.deprecated) -%} -{%- endif %} -@property ({{'nullable, ' if not param.mandatory}}{{param.modifier}}, nonatomic) {{param.type_sdl}}{{param.origin}}{{ending}}; +{%- include 'description_param.jinja' %} +@property ({{'nullable, ' if not param.mandatory}}{{param.modifier}}, nonatomic) {{param.type_sdl}}{{param.origin}}{{' __deprecated' if param.deprecated and param.deprecated is sameas true }}; {% endfor -%} {%- endblock %} @end diff --git a/generator/templates/description.jinja b/generator/templates/description.jinja new file mode 100644 index 000000000..4eff1614e --- /dev/null +++ b/generator/templates/description.jinja @@ -0,0 +1,23 @@ +{% if description or since or history %} +/** + {%- if description %} + {%- for d in description %} + * {{d}} + {%- endfor %}{% endif -%} + {%- if description and ( since or history ) %} + * + {%- endif %} + {%- if deprecated %} + * @deprecated + {%- endif %} + {%- if history %} + * @history SDL {{ history }} + {%- endif %} + {%- if since %} + * @since SDL {{ since }} + {%- endif %} + */ +{%- endif -%} +{%- if deprecated and deprecated is sameas true %} +__deprecated +{%- endif -%} diff --git a/generator/templates/description_param.jinja b/generator/templates/description_param.jinja new file mode 100644 index 000000000..c2926a1f1 --- /dev/null +++ b/generator/templates/description_param.jinja @@ -0,0 +1,20 @@ +{% if param.description or param.since or param.history %} +/** + {%- if param.description %} + {%- for d in param.description %} + * {{d}} + {%- endfor %}{% endif -%} + {%- if param.description and ( param.since or param.history ) %} + * + {%- endif %} + {%- if param.deprecated %} + * @deprecated + {%- endif %} + {%- if param.history %} + * @history SDL {{ param.history }} + {%- endif %} + {%- if param.since %} + * @since SDL {{ param.since }} + {%- endif %} + */ +{%- endif %} diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index f3ee55c9d..7c7ec3eeb 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -5,48 +5,11 @@ #import "{{import}}.h" {%- endfor %} {%- endblock -%} -{%- if deprecated and deprecated is sameas true -%} -{%- set ending = ' __deprecated' -%} -{%- elif deprecated and deprecated is string -%} -{%- set ending = ' __deprecated_msg("{}")'.format(deprecated) -%} -{%- endif %} {%- block body %} -{% if description or since %} -/** - {%- if description %} - {%- for d in description %} - * {{d}} - {%- endfor %}{% endif -%} - {%- if description and since %} - * - {%- endif %} - {%- if deprecated %} - * @deprecated - {%- endif %} - {%- if since %} - * @since SDL {{since}} - {%- endif %} - */ -{%- endif %} +{% include 'description.jinja' %} typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; {% for param in params %} -{%- if param.description or param.since %} -/** - {%- if param.description %} - {%- for d in param.description %} - * {{d}} - {%- endfor %}{% endif -%} - {%- if param.description and param.since %} - * - {%- endif %} - {%- if param.deprecated %} - * @deprecated - {%- endif %} - {%- if param.since %} - * @since SDL {{param.since}} - {%- endif %} - */ -{%- endif %} +{% include 'description_param.jinja' %} extern {{ name }} const {{ name }}{{param.name}}{{ ' %s%s%s'|format('NS_SWIFT_NAME(', param.name|lower, ')') if NS_SWIFT_NAME is defined}}; {% endfor -%} {% endblock -%} \ No newline at end of file diff --git a/generator/templates/enums/template_numeric.h b/generator/templates/enums/template_numeric.h new file mode 100644 index 000000000..bfa24aa32 --- /dev/null +++ b/generator/templates/enums/template_numeric.h @@ -0,0 +1,11 @@ +{% extends "template.h" %} +{%- block body %} +{% include 'description.jinja' %} +typedef NS_ENUM(NSUInteger, SDL{{ name }}){ +{% for param in params %} +{%- macro someop() -%}{% include 'description_param.jinja' %}{%- endmacro -%} +{{ someop()|indent(4, True) }} + SDL{{ name }}{{ param.name }} = {{ param.value }}{{ ' %s%s%s'|format('NS_SWIFT_NAME(', param.name|lower, ')') if NS_SWIFT_NAME is defined}}; +{% endfor -%} +}; +{% endblock -%} \ No newline at end of file diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index 8fd48ec49..c056d14f8 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -56,12 +56,16 @@ def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: render['description'] = self.extract_description(item.description) if item.since: render['since'] = item.since + if item.history: + render['history'] = item.history.pop().since if item.deprecated and item.deprecated.lower() == 'true': render['deprecated'] = True render['params'] = OrderedDict() for param in getattr(item, self.container_name).values(): + if param.name.lower() in ['id']: + param.name = self.minimize_first(item.name) + self.title(param.name) render['params'][param.name] = self.extract_param(param) if isinstance(item, (Struct, Function)): self.extract_imports(param, render['imports']) diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py index 92b72b283..dd072544a 100644 --- a/generator/transformers/enums_producer.py +++ b/generator/transformers/enums_producer.py @@ -38,6 +38,8 @@ def transform(self, item: Enum, render=None) -> dict: render['name'] = name render['imports'] = imports super(EnumsProducer, self).transform(item, render) + if any(map(lambda p: p.value, render['params'])): + render['template'] = 'enums/template_numeric' return render def extract_param(self, param: EnumElement): From a5b671f6d0d2c574c9c0914c0e15b3da7f553cea Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Mon, 2 Mar 2020 11:54:10 +0100 Subject: [PATCH 07/28] changing to flake8 --- generator/README.md | 40 +++++++++++++++++++++ generator/requirements.txt | 4 +-- generator/test/runner.py | 8 ++--- generator/test/test_CodeFormatAndQuality.py | 30 ++++++++-------- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/generator/README.md b/generator/README.md index 1804079bc..843c46960 100644 --- a/generator/README.md +++ b/generator/README.md @@ -1144,3 +1144,43 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` + +## Utils + +### Generator + +Proxy Library RPC Generator inherits any license defined in SDL BASE. + +#### Third Party Licenses + +Both the source and binary distributions of this software contain +some third party software. All the third party software included +or linked is redistributed under the terms and conditions of their +original licenses. + +The third party software included and used by this project is: + +**xmlschema** + +* Licensed under MIT License +* See [https://pypi.org/project/xmlschema/](https://pypi.org/project/xmlschema/) + +**Jinja2** + +* Licensed under BSD License (BSD-3-Clause) +* See [https://pypi.org/project/Jinja2/](https://pypi.org/project/Jinja2/) + +**coverage** + +* Licensed under Apache Software License (Apache 2.0) +* See [https://pypi.org/project/coverage/](https://pypi.org/project/coverage/) + +**pathlib2** + +* Licensed under MIT License +* See [https://pypi.org/project/pathlib2/](https://pypi.org/project/pathlib2/) + +**flake8** + +* Licensed under MIT License +* See [https://pypi.org/project/flake8/](https://pypi.org/project/flake8/) diff --git a/generator/requirements.txt b/generator/requirements.txt index 12a4e48f1..c59028039 100644 --- a/generator/requirements.txt +++ b/generator/requirements.txt @@ -1,5 +1,5 @@ xmlschema -pylint Jinja2 coverage -pathlib2 \ No newline at end of file +pathlib2 +flake8 \ No newline at end of file diff --git a/generator/test/runner.py b/generator/test/runner.py index 62cb20b54..5268b3bb4 100644 --- a/generator/test/runner.py +++ b/generator/test/runner.py @@ -15,7 +15,7 @@ from test_enums import TestEnumsProducer from test_functions import TestFunctionsProducer from test_structs import TestStructsProducer - from test_CodeFormatAndQuality import TestCodeFormatAndQuality + from test_CodeFormatAndQuality import CodeFormatAndQuality except ImportError as error: print('{}.\nProbably you did not initialize submodule'.format(error)) sys.exit(1) @@ -29,8 +29,8 @@ def config_logging(): handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%m-%d %H:%M')) root_logger = logging.getLogger() - handler.setLevel(logging.DEBUG) - root_logger.setLevel(logging.DEBUG) + handler.setLevel(logging.INFO) + root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) @@ -44,7 +44,7 @@ def main(): suite.addTests(TestLoader().loadTestsFromTestCase(TestFunctionsProducer)) suite.addTests(TestLoader().loadTestsFromTestCase(TestStructsProducer)) suite.addTests(TestLoader().loadTestsFromTestCase(TestEnumsProducer)) - suite.addTests(TestLoader().loadTestsFromTestCase(TestCodeFormatAndQuality)) + suite.addTests(TestLoader().loadTestsFromTestCase(CodeFormatAndQuality)) runner = TextTestRunner(verbosity=2) runner.run(suite) diff --git a/generator/test/test_CodeFormatAndQuality.py b/generator/test/test_CodeFormatAndQuality.py index 070e1c36e..b512823c8 100755 --- a/generator/test/test_CodeFormatAndQuality.py +++ b/generator/test/test_CodeFormatAndQuality.py @@ -1,36 +1,38 @@ -# pylint: disable=C0103, C0301, C0115, C0116 """Interface model unit test """ +import subprocess import unittest from os import walk from os.path import join from pathlib import Path -from pylint.lint import Run +from flake8.api import legacy as flake8 -class TestCodeFormatAndQuality(unittest.TestCase): - MINIMUM_SCORE = 9 - +class CodeFormatAndQuality(unittest.TestCase): def setUp(self): """Searching for all python files to be checked """ - self.list_of_files = ['--max-line-length=130', '--disable=import-error'] - root = Path(__file__).absolute().parents[1] - for (directory, _, filenames) in walk(root.as_posix()): + self.list_of_files = [] + for (directory, _, filenames) in walk(Path(__file__).absolute().parents[1].as_posix()): self.list_of_files += [join(directory, file) for file in filenames - if file.endswith('.py') and not file.startswith('test') + if file.endswith('.py') and 'test' not in directory and 'rpc_spec' not in directory] - def test_pylint_conformance(self): - """Performing checks by PyLint + def test_check(self): + """Performing checks by flake8 """ - results = Run(self.list_of_files, do_exit=False) - score = results.linter.stats['global_note'] - self.assertGreaterEqual(score, self.MINIMUM_SCORE) + + style_guide = flake8.get_style_guide(max_line_length=120) + report = style_guide.check_files(self.list_of_files) + self.assertEqual(report.total_errors, 0) + + process = subprocess.Popen(["flake8", '--exclude=rpc_spec,test', '..'], stdout=subprocess.PIPE) + (output, err) = process.communicate() + print(output.decode("utf-8")) if __name__ == '__main__': From 3c6cd6d16aef99044381a6b869abe6f077c4fc04 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Fri, 6 Mar 2020 22:41:34 +0100 Subject: [PATCH 08/28] Update generator/README.md Co-Authored-By: Joel Fischer --- generator/README.md | 501 ++++++++------------------------------------ 1 file changed, 88 insertions(+), 413 deletions(-) diff --git a/generator/README.md b/generator/README.md index 843c46960..fdbbfc87b 100644 --- a/generator/README.md +++ b/generator/README.md @@ -2,21 +2,21 @@ ## Overview -This script provides a possibility to auto-generate Objective-C code (header \*.h and implementation \*.m classes) based on the SDL MOBILE_API XML specification provided as a source of true. +This script provides a possibility to auto-generate Objective-C code (header \*.h and implementation \*.m classes) based on the SDL MOBILE_API XML specification provided as a source of truth. ## Requirements The script requires **Python 3.5** pre-installed on the host system. This is the minimal **Python 3** version that has not yet reached the end-of-life (https://devguide.python.org/devcycle/#end-of-life-branches). -Note: two pyton versions can be installed on the system and to use the required third version one must use the **pip3** command instead of the commonly used **pip**. +Note: To install versions of Python 3, you must use the **pip3** command. All required libraries are listed in `requirements.txt` and should be pre-installed on the system prior to using the sript. Please use the following command to install the libraries: ```shell script -$ pip3 install -r requirements.txt +$ pip3 install -r generator/requirements.txt ``` -Please also make sure all git submodules are installed and up to date since the script uses the XML parser provided there. +Please also make sure all git submodules are installed and up to date since the script uses the XML parser provided in a submodule. ```shell script $ git submodule update --init --recursive @@ -28,7 +28,6 @@ or $ git submodule update --recursive ``` - ## Usage **Usage example** @@ -38,13 +37,11 @@ $ cd sdl_ios $ python3 generator/generator.py -xml generator/rpc_spec/MOBILE_API.xml -xsd generator/rpc_spec/MOBILE_API.xsd -d output_dir ``` -*Note: one may skip the first item python3 if the host system configured to run \*.py files on the Python-3 engine.* -As a result the output_dir will have all the new generated files (at the time of this article writing as many as 716 files were produced). +As a result the output_dir will have all the new generated files. **Detailed usage description (keys, options)** - ``` usage: generator.py [-h] [-v] [-xml SOURCE_XML] [-xsd SOURCE_XSD] [-d OUTPUT_DIRECTORY] [-t [TEMPLATES_DIRECTORY]] @@ -78,65 +75,31 @@ optional arguments: ### How to use the generated classes -Since all RPC classes used in **SmartDeviceLink iOS** library were created manually due to historical reasons the generated files might and will differ from the original ones. The code generator takes all efforts possible to produce source code as close to the original as possible and it already has maps (dictionaries) to rename classes, data structures that do not follow the naming rules though it is not quite possible since the source of true is the XML file and anything that goes beyond it is hard if possible at all to handle. To incorporate the generated files into the project one must use a diff tool (Meld, FileMerge, you name it) and compare the 2 sets of RPC files and check what changed and which changes should go upstream. The last but no least chances are there appear new generated files that are not present in the project. If such a case one must add those files to Xcode project manually and place them in proper groups sorting the files by their kind. Note: the groups are just virtual folders and due to historical reasons the ones do not map to file system folders so phisically all files go to the root folder and when they get added to the project they should go to the following folders: - * Enums - * Structs - * Notification - * Requests - * Responses +All RPC classes used in **SmartDeviceLink iOS** library were created manually due to historical reasons and have public API differences from the RPC_SPEC. Therefore, the generated files will differ from the current ones. The generated files are based on the RPC_SPEC and do not contain changes to match the existing files. Therefore, do not replace existing files with generated files. If you want to update existing files with new parameters using the generator, you must generate the file and then use a diff tool to add only the new information and not to change existing information. -Due to the explosion of complexity and code that is involved with mapping every exception that has ever been made in the iOS project compared to the RPC spec, the decision was made that the generator should not pursue those mappings. This has several implications: +If you are adding new RPCs entirely, you can generate those RPCs. Use the `--skip` switch to only generate new files. You must add those files to Xcode project, SmartDeviceLink.h, and podspec files manually and place them in proper groups sorting the files by their kind. Note: the groups are just virtual folders; they do not map to the file system, so all files go to the SmartDeviceLink folder on the file system. - * The generator will not override the existing RPCs. All newly created structs, enums, requests, responses, and notifications will be generated, but new parameters to existing RPCs will have to be manually handled. The script's `--skip` command line switch will be used. - * Existing unit tests cannot be used to verify the output. -The idea is that a future major version could involve a switch-over to using all generated code instead of the current code. It is not perfect for the current version but it is helpful for adding new RPCs. -Any major version change cannot be done that would be involved with overwriting existing RPC classes and to map out all the differences would be a huge time and complexity-sink that the PM and PR author agreed was feasible but unmaintainable and undesirable. +## Objective-C transformation rules -# Objetive-C transformation rules - -## Overview -These are the general transformation rules for SDL RPC classes Objective-C Library. The base classes description already included in the library is not provided here for more one may want to view the source code. +### Overview +These are the general transformation rules for SDL RPC classes Objective-C Library. For more information about the base classes for these RPCs, you can look in the app library. ## Output Directory Structure and Package definitions +The script creates corresponding RPC classes of ``, `` and `` elements following the `MOBILE_API.xml` rules. According to existing structure of sdl_ios library the output directory will contain the following files (plain structure, no subfolders). -The script creates corresponding RPC classes of ``, `` and `` elements following the `MOBILE_API.xml` rules. According to existing structure of SmartDeviceLink library the output directory will contain the following files (plain structure, no subfolders). For enum, struct, function future naming, generally it should be named - -```SDL``` - - in camel case. - - -**Requests:** - - * SDLxxx.h - * SDLxxx.m - -**Responses:** - - * SDLxxxResponse.h - * SDLxxxResponse.m - -*Note: as an exception from rules this kind has the suffix 'Response'* - -**Structures:** - - * SDLxxx.h - * SDLxxx.m - -**Enums:** +RPC requests, responses, structs, enums, and notifications file names all have the form: - * SDLxxx.h - * SDLxxx.m +* SDLxxx.h +* SDLxxx.m -**Notifications:** +Responses have the form: - * SDLxxx.h - * SDLxxx.m +* SDLxxxResponse.h +* SDLxxxResponse.m Where the **xxx** is the correspondent item name. - ## The License Header All files should begin with the license information. @@ -174,9 +137,8 @@ All files should begin with the license information. * POSSIBILITY OF SUCH DAMAGE. */ ``` -Where `[year]` in the copyright line is the current (?) year. -*Note: for simplisity sake the header removed from Objective-C code snippets in the following sections of this document* +Where `[year]` in the copyright line is the current (?) year. ### General rules for Objective-C classes 1. Default initializer in every class @@ -190,16 +152,16 @@ Where `[year]` in the copyright line is the current (?) year. *Pease note the required double brackets in the if statement* 2. Initializer for mandatory params if there is/are any in XML (skipped if no params) - 3. Initializer for all params if there is/are any which is not mandatory in XML (skipped if no params) -## Scalars. +#### Scalars There are 4 type of scalar values declared in the SDL lib. These are: 1. **SDLInt** - A declaration that this NSNumber contains an NSInteger. 0. **SDLUInt** - A declaration that this NSNumber contains an NSUInteger. 0. **SDLBool** - A declaration that this NSNumber contains a BOOL. 0. **SDLFloat** - A declaration that this NSNumber contains a float. -*Note: the ones declared as empty protocols and NSNumber conforms to them.* +*Note: These are syntactic sugar to help the developer know what type of value is held in the `NSNumber`.* + ```objc @interface NSNumber (NumberType) ``` @@ -211,26 +173,22 @@ or an array: ```objc @property (strong, nonatomic) NSArray *> *timeStamp; ``` -*Note: the 4 scalar values implemented as empty protocols and 'extend' the* **NSNumber** *class.* -## Enums -So called enums in **sdl\_ios** implemented as strings (NSString) typedefed with a proper enum type. One may notice it is not real enums but NSString objects though in SWIFT they must become real enums. +#### Enums +RPC Enums in SDL are strings. sdl_ios uses `NSString` `typedef`ed with a proper enum type. In Swift projects, however, they become real enums by using the `NS_SWIFT_ENUM` compiler tag. -*Example:* +*Base definition of `SDLEnum`:* ```objc typedef NSString* SDLEnum SDL_SWIFT_ENUM; -``` -*Note: This new defined type has already had an asterisk at the end so anything that inherits from SDLEnum needs no asterisk.* +*Note: This new defined type has already adds a pointer, so anything that inherits from `SDLEnum` needs no asterisk.* ```objc -typedef SDLEnum SDLTouchType SDL_SWIFT_ENUM; +typedef SDLEnum SDLTouchType SDL_SWIFT_ENUM; // SDLTouchType will be considered an NSString by the compiler in Obj-C, but will be an enum object of type SDLTouchType in Swift. ``` -*Note: The compiler considers SDLTouchType as NSString** - -And here is a concrete 'enum' item +And here is a concrete 'enum' item: ```objc extern SDLTouchType const SDLTouchTypeBegin; @@ -248,24 +206,6 @@ or even: extern SDLTouchType const SDLTouchTypeBegin __deprecated_msg(("this item is deprecated once and for all, please use SDLTouchTypeNewType instead")); ``` - -Each Enum class is stored in two files (header \*.h and implementation \*.m) and the filename and the class name should be composed based on the value from the `"name"` attribute of ``. - -Example: - -``` - -./SDLImageType.h -./SDLImageType.m -``` - -Each Enum class should include the enum definition, see for example the AmbientLightStatus enum: - -```objc -#import "SDLEnum.h" -typedef SDLEnum SDLAmbientLightStatus SDL_SWIFT_ENUM; -``` - Take for an instance the enum class KeypressMode ```xml @@ -274,36 +214,25 @@ Take for an instance the enum class KeypressMode Each keypress is individually sent as the user presses the keyboard keys. - - The keypresses are queued and a string is eventually sent once the user chooses to submit their entry. - - - The keypresses are queue and a string is sent each time the user presses a keyboard key; the string contains the entire current entry. - + ``` -In the following example +In the following example, we would define in the header: ```objc extern SDLKeypressMode const SDLKeypressModeSingleKeypress; ``` -The **SDLKeypressModeSingleKeypress** enum is seen by the compiler as: - -```objc -extern NSString* const SDLKeypressModeSingleKeypress; -``` - -and **SDLKeypressModeSingleKeypress** itself must be implemented in the correspondent SDLKeypressMode.m file as a string: +and `SDLKeypressModeSingleKeypress` itself must be implemented in the correspondent `SDLKeypressMode.m ` file like so: ```objc SDLKeypressMode const SDLKeypressModeSingleKeypress = @"SINGLE_KEYPRESS"; ``` -## Structures +#### Structs -Structures in **sdl_ios** implemented as classes derived from the parent class SDLRPCStruct with all parameters implemented as "@property". Let us take for an instance the **DeviceInfo** structure. In the XML it is declared as following: +Structures in sdl_ios are implemented as classes derived from the parent class SDLRPCStruct with all parameters implemented as `@property`. Let us take for an instance the `DeviceInfo` structure. In the XML it is declared as following: ```xml @@ -330,9 +259,8 @@ Structures in **sdl_ios** implemented as classes derived from the parent class ``` *Please note that all params declared as mandatory="false" and there is one init method with all the params in the generated file. The method* ```+ (instancetype)currentDevice;``` *comes from the old manually made implementation* - *All method and property descriptions generated from the xml descriptions* - *Note: the file begins with the* **NS_ASSUME_NONNULL_BEGIN** *macro which makes all properties / parameters mandatory. If a parameter is not mandatory then the modifier* **nullable** *will be used* +*Note: the file begins with the `NS_ASSUME_NONNULL_BEGIN` macro, which makes all properties / parameters mandatory. If a parameter is not mandatory, then the modifier `nullable` must be used* ```objc // SDLDeviceInfo.h @@ -514,28 +442,26 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` -## Functions -Functions in iOS implemented as 3 different classes grouped by its kind, all the 3 extend the common parent class **SDLRPCMessage** -```objc +#### Functions + +Functions in iOS are implemented as 3 different classes grouped by their respective type. All the 3 extend the common parent class `SDLRPCMessage`. + // This is the parent class for all the 3 function kinds -SDLRPCMessage : SDLRPCStruct : NSObject +SDLRPCMessage : SDLRPCStruct : NSObject SDLRPCNotification : SDLRPCMessage /// An RPC sent from the head unit to the app about some data change, such as a button was pressed SDLRPCRequest : SDLRPCMessage /// Superclass of RPC requests SDLRPCResponse : SDLRPCMessage /// Superclass of RPC responses ``` -*Note: for some reason SDLRPCMessage conforms to NSCopying protocol twice.* -First of all there is the **FunctionID** class generated though it is not declared in the XML. This class maps all function IDs that are integers to function names as strings. +First of all there is the `SDLFunctionID` class generated though it is not declared in the XML. This class maps all function IDs that are integers to function names as strings. 1. Uses of the `"name"` attribute should be normalized by the removal of the ID suffix, e.g. `RegisterAppInterfaceID -> RegisterAppInterface`. - -0. The constant name should be camel case formatted. - +2. The constant name should be camel case formatted. 0. The constant has 2 fields the first is the `int` value of the `"value"` attribute and the second is the `String` value of normalized `"name"` attribute. -Internally it uses another file that lists all the function names **SDLRPCFunctionNames** +Internally it uses another file that lists all the function names `SDLRPCFunctionNames`. ```objc // SDLFunctionID.h @@ -568,7 +494,7 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` -Each from MOBILE_API.XML is declared in SDLRPCFunctionNames.h and SDLRPCFunctionNames.m files. It exports function names as strings. +Each from MOBILE_API.XML is declares its function name in `SDLRPCFunctionNames.h` and `SDLRPCFunctionNames.m` files. ```objc SDLRPCFunctionNames.h @@ -603,77 +529,7 @@ SDLRPCFunctionName const SDLRPCFunctionNameAddSubMenu = @"AddSubMenu"; . . . and so on ``` - -### Request Functions (SDLRPCRequest) - -#### Without parameters - -The templates from the table below can be used as a basic for each Request class which can be generated from MOBILE_API.XML -Two different file types with (".h" amd ".m" extensions) need to be created for each function in the XML. - -```xml - - - Requests the current list of resident filenames for the registered app. - Not supported on first generation SDL enabled vehicles. - - -``` - -Declaration file (.h) - -```objc -// SDLListFiles.h -// - -#import "SDLRPCRequest.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * Requests the current list of resident filenames for the registered app. Not supported on first generation SDL - * enabled vehicles. - * - * @since SDL 3.0.0 - */ -@interface SDLListFiles : SDLRPCRequest - -@end - -NS_ASSUME_NONNULL_END -``` - -Implementation file (.m) - -```objc -// SDLListFiles.m - -#import "SDLListFiles.h" -#import "NSMutableDictionary+Store.h" -#import "SDLRPCFunctionNames.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation SDLListFiles - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionNameListFiles])) { - } - return self; -} -#pragma clang diagnostic pop - -@end - -NS_ASSUME_NONNULL_END -``` - - - - - +##### Request Functions (SDLRPCRequest) ##### Function with simple params @@ -688,9 +544,6 @@ This section depicts all functions which include one or a few parameters of the ``` -### Request Functions (SDLRPCRequest) - -The parent class is **SDLRPCRequest** ```objc // SDLGetCloudAppProperties.h @@ -761,166 +614,47 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` -### Response Functions (SDLRPCResponse) - -The parent class is **SDLRPCResponse** - -Simple response example **AddCommand**, with no arguments: - -```xml - - - - true if successful; false, if failed - - - - See Result - - - - - - - - - - - - - - - - Provides additional human readable info regarding the result. - - - -``` - -```objc -// SDLAddCommandResponse.h -// - -#import "SDLRPCResponse.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * @since SDL 1.0.0 - */ -@interface SDLAddCommandResponse : SDLRPCResponse - -@end - -NS_ASSUME_NONNULL_END -``` - -```objc -// SDLAddCommandResponse.m -// - -#import "SDLAddCommandResponse.h" -#import "NSMutableDictionary+Store.h" -#import "SDLRPCFunctionNames.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation SDLAddCommandResponse - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionNameAddCommand])) { - } - return self; -} -#pragma clang diagnostic pop - -@end - -NS_ASSUME_NONNULL_END -``` - -As one may notice this class does not implement too much since the main logic is already implemented in its parent class: - -```objc -// SDLRPCResponse.h -// - -#import "SDLRPCMessage.h" -#import "SDLResult.h" - -NS_ASSUME_NONNULL_BEGIN - -/// Superclass of RPC responses -@interface SDLRPCResponse : SDLRPCMessage - -/** - * The correlation id of the corresponding SDLRPCRequest. - */ -@property (strong, nonatomic) NSNumber *correlationID; - -/** - * Whether or not the SDLRPCRequest was successful. - */ -@property (strong, nonatomic) NSNumber *success; - -/** - * The result of the SDLRPCRequest. If the request failed, the result code contains the failure reason. - */ -@property (strong, nonatomic) SDLResult resultCode; - -/** - * More detailed success or error message. - */ -@property (nullable, strong, nonatomic) NSString *info; +##### Response Functions (SDLRPCResponse) -@end - -NS_ASSUME_NONNULL_END -``` - -#### Response class with extra argument example (Alert Response): +##### Response class with extra argument example (Alert Response): ```xml - - - - true if successful; false, if failed - - - - See Result - - - - - - - - - - - - - - - - Provides additional human readable info regarding the result. - - - - - Amount of time (in seconds) that an app must wait before resending an alert. - If provided, another system event or overlay currently has a higher priority than this alert. - An app must not send an alert without waiting at least the amount of time dictated. - - - - + + + true if successful; false, if failed + + + + See Result + + + + + + + + + + + + + + + + Provides additional human readable info regarding the result. + + + + + Amount of time (in seconds) that an app must wait before resending an alert. + If provided, another system event or overlay currently has a higher priority than this alert. + An app must not send an alert without waiting at least the amount of time dictated. + + + ``` -As we can see it in the XML it requites an additional param **tryAgainTime** which is declared and implemented in the **SDLAlertResponse** class as follows: +As we can see it in the XML it requires an additional param `tryAgainTime` which is declared and implemented in the `SDLAlertResponse` class as follows: ```objc // SDLAlertResponse.h @@ -993,78 +727,19 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` -### Notification Functions (SDLRPCNotification) - -The parent class is **SDLRPCNotification** - +##### Notification Functions (SDLRPCNotification) -Let us take for an instance the class **SDLOnAudioPassThru**. It is declared in the XML as follows. It does not look as much as anything and so its implementation. - -```xml - - Binary data is in binary part of hybrid msg - -``` - -The corresponding Objective-C class **SDLOnAudioPassThru** looks as following. - -```objc -// SDLOnAudioPassThru.h - -#import "SDLRPCNotification.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * Binary data is in binary part of hybrid msg - * - * @since SDL 2.0.0 - */ -@interface SDLOnAudioPassThru : SDLRPCNotification - -@end - -NS_ASSUME_NONNULL_END -``` - -```objc -// SDLOnAudioPassThru.m - -#import "SDLOnAudioPassThru.h" -#import "NSMutableDictionary+Store.h" -#import "SDLRPCParameterNames.h" -#import "SDLRPCFunctionNames.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation SDLOnAudioPassThru - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionNameOnAudioPassThru])) { - } - return self; -} -#pragma clang diagnostic pop - -@end - -NS_ASSUME_NONNULL_END -``` - - -Another example is the class **SDLOnAppInterfaceUnregistered**. It is declared in the XML as follows. +Another example is the class `SDLOnAppInterfaceUnregistered`. It is declared in the XML as follows: ```xml - - See AppInterfaceUnregisteredReason - - + + See AppInterfaceUnregisteredReason + + ``` -The corresponding Objective-C class **SDLOnAppInterfaceUnregistered** looks as following. +The corresponding Objective-C class `SDLOnAppInterfaceUnregistered` looks as following. ```objc // SDLOnAppInterfaceUnregistered.h @@ -1145,11 +820,11 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` -## Utils +## Other Utilities ### Generator -Proxy Library RPC Generator inherits any license defined in SDL BASE. +Proxy Library RPC Generator inherits the license defined in the root folder of this project. #### Third Party Licenses From 50a2d3ea9e7bd7f95cc8f693856ef7451085f736 Mon Sep 17 00:00:00 2001 From: Vladyslav Mustafin Date: Tue, 10 Mar 2020 16:30:40 +0200 Subject: [PATCH 09/28] Update generator/README.md Python version --- generator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/README.md b/generator/README.md index fdbbfc87b..877cdb023 100644 --- a/generator/README.md +++ b/generator/README.md @@ -6,7 +6,7 @@ This script provides a possibility to auto-generate Objective-C code (header \*. ## Requirements -The script requires **Python 3.5** pre-installed on the host system. This is the minimal **Python 3** version that has not yet reached the end-of-life (https://devguide.python.org/devcycle/#end-of-life-branches). +The script requires **Python 3** pre-installed on the host system. The minimal supported Python 3 version is **3.7.6**. It may work on versions back to 3.5 (the minimal version that has not yet reached [the end-of-life](https://devguide.python.org/devcycle/#end-of-life-branches)), but this is not supported and may break in the future. Note: To install versions of Python 3, you must use the **pip3** command. From abb19f31a42d7d41122109e8a4993b7a4fb266cb Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 10 Mar 2020 20:30:26 +0100 Subject: [PATCH 10/28] changing according to review comments --- generator/README.md | 97 ++++++++---------------- generator/generator.py | 8 +- generator/templates/copyright.txt | 60 +++++++-------- generator/templates/functions/template.m | 5 +- 4 files changed, 67 insertions(+), 103 deletions(-) diff --git a/generator/README.md b/generator/README.md index 877cdb023..815a89bdc 100644 --- a/generator/README.md +++ b/generator/README.md @@ -104,9 +104,9 @@ Where the **xxx** is the correspondent item name. All files should begin with the license information. -``` +```jinja2 /* - * Copyright (c) 2017 - [year], SmartDeviceLink Consortium, Inc. + * Copyright (c) {{year}}, SmartDeviceLink Consortium, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -141,11 +141,13 @@ All files should begin with the license information. Where `[year]` in the copyright line is the current (?) year. ### General rules for Objective-C classes -1. Default initializer in every class +1. Default initializer applies only to Functions(Request / Response / Notification) classes + ```objc - (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionNameRegisterAppInterface])) { - } + self = [super initWithName:SDLRPCFunctionNameRegisterAppInterface]; + if (!self) { return nil; } + return self; } ``` @@ -197,13 +199,8 @@ extern SDLTouchType const SDLTouchTypeBegin; If an item is deprecated then it will be declared as such: ```objc -extern SDLTouchType const SDLTouchTypeBegin __deprecated; -``` - -or even: - -```objc -extern SDLTouchType const SDLTouchTypeBegin __deprecated_msg(("this item is deprecated once and for all, please use SDLTouchTypeNewType instead")); +__deprecated +extern SDLTouchType const SDLTouchTypeBegin; ``` Take for an instance the enum class KeypressMode @@ -258,13 +255,10 @@ Structures in sdl_ios are implemented as classes derived from the parent class S ``` - *Please note that all params declared as mandatory="false" and there is one init method with all the params in the generated file. The method* ```+ (instancetype)currentDevice;``` *comes from the old manually made implementation* - *Note: the file begins with the `NS_ASSUME_NONNULL_BEGIN` macro, which makes all properties / parameters mandatory. If a parameter is not mandatory, then the modifier `nullable` must be used* ```objc // SDLDeviceInfo.h -// #import "SDLRPCStruct.h" @@ -277,18 +271,13 @@ NS_ASSUME_NONNULL_BEGIN */ @interface SDLDeviceInfo : SDLRPCStruct -/// Convenience init. Object will contain all information about the connected device automatically. -/// -/// @return An SDLDeviceInfo object -+ (instancetype)currentDevice; - /** - * @param hardware - * @param firmwareRev - * @param os - * @param osVersion - * @param carrier - * @param @(maxNumberRFCOMMPorts) + * @param hardware - hardware + * @param firmwareRev - firmwareRev + * @param os - os + * @param osVersion - osVersion + * @param carrier - carrier + * @param maxNumberRFCOMMPorts - @(maxNumberRFCOMMPorts) * @return A SDLDeviceInfo object */ - (instancetype)initWithHardware:(nullable NSString *)hardware firmwareRev:(nullable NSString *)firmwareRev os:(nullable NSString *)os osVersion:(nullable NSString *)osVersion carrier:(nullable NSString *)carrier maxNumberRFCOMMPorts:(UInt8)maxNumberRFCOMMPorts; @@ -296,48 +285,36 @@ NS_ASSUME_NONNULL_BEGIN /** * Device model * {"default_value": null, "max_length": 500, "min_length": 0} - * - * Optional, NSString * */ @property (nullable, strong, nonatomic) NSString *hardware; /** * Device firmware revision * {"default_value": null, "max_length": 500, "min_length": 0} - * - * Optional, NSString * */ @property (nullable, strong, nonatomic) NSString *firmwareRev; /** * Device OS * {"default_value": null, "max_length": 500, "min_length": 0} - * - * Optional, NSString * */ @property (nullable, strong, nonatomic) NSString *os; /** * Device OS version * {"default_value": null, "max_length": 500, "min_length": 0} - * - * Optional, NSString * */ @property (nullable, strong, nonatomic) NSString *osVersion; /** * Device mobile carrier (if applicable) * {"default_value": null, "max_length": 500, "min_length": 0} - * - * Optional, NSString * */ @property (nullable, strong, nonatomic) NSString *carrier; /** * Omitted if connected not via BT. * {"default_value": null, "max_value": 100, "min_value": 0} - * - * Optional, UInt8 */ @property (nullable, strong, nonatomic) NSNumber *maxNumberRFCOMMPorts; @@ -350,7 +327,6 @@ The implementation **SDLDeviceInfo.m** file: ```objc // SDLDeviceInfo.m -// #import "SDLDeviceInfo.h" #import "NSMutableDictionary+Store.h" @@ -360,22 +336,6 @@ NS_ASSUME_NONNULL_BEGIN @implementation SDLDeviceInfo - -+ (instancetype)currentDevice { - static SDLDeviceInfo *deviceInfo = nil; - if (deviceInfo == nil) { - deviceInfo = [[SDLDeviceInfo alloc] init]; - deviceInfo.hardware = [UIDevice currentDevice].model; - deviceInfo.os = [UIDevice currentDevice].systemName; - deviceInfo.osVersion = [UIDevice currentDevice].systemVersion; - CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init]; - CTCarrier *carrier = netinfo.subscriberCellularProvider; - NSString *carrierName = carrier.carrierName; - deviceInfo.carrier = carrierName; - } - return deviceInfo; -} - - (instancetype)initWithHardware:(nullable NSString *)hardware firmwareRev:(nullable NSString *)firmwareRev os:(nullable NSString *)os osVersion:(nullable NSString *)osVersion carrier:(nullable NSString *)carrier maxNumberRFCOMMPorts:(UInt8)maxNumberRFCOMMPorts { self = [super init]; if (!self) { @@ -399,27 +359,27 @@ NS_ASSUME_NONNULL_BEGIN } - (void)setFirmwareRev:(nullable NSString *)firmwareRev { - [self.store sdl_setObject:firmwareRev forName:SDLRPCParameterNameFirmwareRevision]; + [self.store sdl_setObject:firmwareRev forName:SDLRPCParameterNameFirmwareRev]; } - (nullable NSString *)firmwareRev { - return [self.store sdl_objectForName:SDLRPCParameterNameFirmwareRevision ofClass:NSString.class error:nil]; + return [self.store sdl_objectForName:SDLRPCParameterNameFirmwareRev ofClass:NSString.class error:nil]; } - (void)setOs:(nullable NSString *)os { - [self.store sdl_setObject:os forName:SDLRPCParameterNameOS]; + [self.store sdl_setObject:os forName:SDLRPCParameterNameOs]; } - (nullable NSString *)os { - return [self.store sdl_objectForName:SDLRPCParameterNameOS ofClass:NSString.class error:nil]; + return [self.store sdl_objectForName:SDLRPCParameterNameOs ofClass:NSString.class error:nil]; } - (void)setOsVersion:(nullable NSString *)osVersion { - [self.store sdl_setObject:osVersion forName:SDLRPCParameterNameOSVersion]; + [self.store sdl_setObject:osVersion forName:SDLRPCParameterNameOsVersion]; } - (nullable NSString *)osVersion { - return [self.store sdl_objectForName:SDLRPCParameterNameOSVersion ofClass:NSString.class error:nil]; + return [self.store sdl_objectForName:SDLRPCParameterNameOsVersion ofClass:NSString.class error:nil]; } - (void)setCarrier:(nullable NSString *)carrier { @@ -594,8 +554,9 @@ NS_ASSUME_NONNULL_BEGIN #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionNameGetCloudAppProperties])) { - } + self = [super initWithName:SDLRPCFunctionNameGetCloudAppProperties]; + if (!self) { return nil; } + return self; } #pragma clang diagnostic pop @@ -708,8 +669,9 @@ NS_ASSUME_NONNULL_BEGIN #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionNameAlert])) { - } + self = [super initWithName:SDLRPCFunctionNameShowAppMenu]; + if (!self) { return nil; } + return self; } #pragma clang diagnostic pop @@ -791,8 +753,9 @@ NS_ASSUME_NONNULL_BEGIN #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionNameOnAppInterfaceUnregistered])) { - } + self = [super initWithName:SDLRPCFunctionNameOnAppInterfaceUnregistered]; + if (!self) { return nil; } + return self; } #pragma clang diagnostic pop diff --git a/generator/generator.py b/generator/generator.py index d9c91ac61..85a27342b 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -16,7 +16,7 @@ from jinja2 import UndefinedError, TemplateNotFound, FileSystemLoader, Environment, ChoiceLoader, \ TemplateAssertionError, TemplateSyntaxError, TemplateRuntimeError -from pathlib2 import Path +from pathlib import Path ROOT = Path(__file__).absolute().parents[0] @@ -71,7 +71,7 @@ def output_directory(self, output_directory): if output_directory.startswith('/'): path = Path(output_directory).absolute().resolve() else: - path = ROOT.joinpath(output_directory).resolve() + path = Path('.').absolute().joinpath(output_directory).resolve() if not path.exists(): self.logger.warning('Directory not found: %s, trying to create it', path) try: @@ -202,7 +202,7 @@ def get_parser(self): if not getattr(args, kind.name) and kind.path.exists(): while True: try: - confirm = input('Confirm default path {} for {} Y/Enter = yes, N = no' + confirm = input('Confirm default path {} for {} [Y/n]:\t' .format(kind.path, kind.name)) if confirm.lower() == 'y' or not confirm: print('{} set to {}'.format(kind.name, kind.path)) @@ -378,7 +378,7 @@ async def process_common(self, skip, overwrite, file_with_suffix, data, template else: while True: try: - confirm = input('File already exists {}. Overwrite? Y/Enter = yes, N = no\n' + confirm = input('File already exists {}. Overwrite? [Y/n]:\t' .format(file_with_suffix.name)) if confirm.lower() == 'y' or not confirm: self.logger.info('Overriding %s', file_with_suffix.name) diff --git a/generator/templates/copyright.txt b/generator/templates/copyright.txt index abb41f0da..a037d487d 100644 --- a/generator/templates/copyright.txt +++ b/generator/templates/copyright.txt @@ -1,31 +1,31 @@ /* -* Copyright (c) {{year}}, SmartDeviceLink Consortium, Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* Redistributions of source code must retain the above copyright notice, this -* list of conditions and the following disclaimer. -* -* Redistributions in binary form must reproduce the above copyright notice, -* this list of conditions and the following -* disclaimer in the documentation and/or other materials provided with the -* distribution. -* -* Neither the name of the SmartDeviceLink Consortium Inc. nor the names of -* its contributors may be used to endorse or promote products derived -* from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -* POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file + * Copyright (c) {{year}}, SmartDeviceLink Consortium, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the SmartDeviceLink Consortium Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ \ No newline at end of file diff --git a/generator/templates/functions/template.m b/generator/templates/functions/template.m index 3e5e4bd1c..07fb962be 100644 --- a/generator/templates/functions/template.m +++ b/generator/templates/functions/template.m @@ -8,8 +8,9 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (instancetype)init { - if ((self = [super initWithName:SDLRPCFunctionName{{origin}}])) { - } + self = [super initWithName:SDLRPCFunctionName{{origin}}]; + if (!self) { return nil; } + return self; } #pragma clang diagnostic pop From 9dc248e803135a090adcb8d7bf375eee246b1e62 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 17 Mar 2020 10:31:54 +0100 Subject: [PATCH 11/28] Apply suggestions from code review update README.md Co-Authored-By: Joel Fischer --- generator/README.md | 57 ++++++++++++++------------------------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/generator/README.md b/generator/README.md index 815a89bdc..7f7989b7b 100644 --- a/generator/README.md +++ b/generator/README.md @@ -1,8 +1,6 @@ # Proxy Library RPC Generator -## Overview - -This script provides a possibility to auto-generate Objective-C code (header \*.h and implementation \*.m classes) based on the SDL MOBILE_API XML specification provided as a source of truth. +This script provides the ability to auto-generate Objective-C RPC code (header \*.h and implementation \*.m classes) based on the SDL MOBILE_API XML specification. ## Requirements @@ -85,7 +83,7 @@ If you are adding new RPCs entirely, you can generate those RPCs. Use the `--ski ### Overview These are the general transformation rules for SDL RPC classes Objective-C Library. For more information about the base classes for these RPCs, you can look in the app library. -## Output Directory Structure and Package definitions +### Output Directory Structure and Package definitions The script creates corresponding RPC classes of ``, `` and `` elements following the `MOBILE_API.xml` rules. According to existing structure of sdl_ios library the output directory will contain the following files (plain structure, no subfolders). RPC requests, responses, structs, enums, and notifications file names all have the form: @@ -100,7 +98,7 @@ Responses have the form: Where the **xxx** is the correspondent item name. -## The License Header +### The License Header All files should begin with the license information. @@ -138,20 +136,19 @@ All files should begin with the license information. */ ``` -Where `[year]` in the copyright line is the current (?) year. +Where `{{year}}` in the copyright line is the current year. ### General rules for Objective-C classes 1. Default initializer applies only to Functions(Request / Response / Notification) classes ```objc - - (instancetype)init { - self = [super initWithName:SDLRPCFunctionNameRegisterAppInterface]; - if (!self) { return nil; } +- (instancetype)init { + self = [super initWithName:SDLRPCFunctionNameRegisterAppInterface]; + if (!self) { return nil; } - return self; - } + return self; +} ``` -*Pease note the required double brackets in the if statement* 2. Initializer for mandatory params if there is/are any in XML (skipped if no params) 3. Initializer for all params if there is/are any which is not mandatory in XML (skipped if no params) @@ -162,16 +159,15 @@ There are 4 type of scalar values declared in the SDL lib. These are: 0. **SDLUInt** - A declaration that this NSNumber contains an NSUInteger. 0. **SDLBool** - A declaration that this NSNumber contains a BOOL. 0. **SDLFloat** - A declaration that this NSNumber contains a float. + *Note: These are syntactic sugar to help the developer know what type of value is held in the `NSNumber`.* -```objc -@interface NSNumber (NumberType) -``` Usage example: ```objc @property (strong, nonatomic) NSNumber *touchEventId; ``` -or an array: + +or in an array: ```objc @property (strong, nonatomic) NSArray *> *timeStamp; ``` @@ -179,7 +175,7 @@ or an array: #### Enums RPC Enums in SDL are strings. sdl_ios uses `NSString` `typedef`ed with a proper enum type. In Swift projects, however, they become real enums by using the `NS_SWIFT_ENUM` compiler tag. -*Base definition of `SDLEnum`:* +Base definition of `SDLEnum`: ```objc typedef NSString* SDLEnum SDL_SWIFT_ENUM; @@ -405,21 +401,15 @@ NS_ASSUME_NONNULL_END #### Functions -Functions in iOS are implemented as 3 different classes grouped by their respective type. All the 3 extend the common parent class `SDLRPCMessage`. +Functions in iOS are implemented as 3 different classes (`SDLRPCRequest`, `SDLRPCResponse`, and `SDLRPCNotification`) grouped by their respective type. All the 3 extend the common parent class `SDLRPCMessage`. -// This is the parent class for all the 3 function kinds -SDLRPCMessage : SDLRPCStruct : NSObject - -SDLRPCNotification : SDLRPCMessage /// An RPC sent from the head unit to the app about some data change, such as a button was pressed -SDLRPCRequest : SDLRPCMessage /// Superclass of RPC requests -SDLRPCResponse : SDLRPCMessage /// Superclass of RPC responses -``` -First of all there is the `SDLFunctionID` class generated though it is not declared in the XML. This class maps all function IDs that are integers to function names as strings. +##### Function ID, Function Name, and Parameter Name Special Case Class +There is also the `SDLFunctionID` class generated though it is not declared in the XML. This class maps all function IDs that are integers to function names as strings. 1. Uses of the `"name"` attribute should be normalized by the removal of the ID suffix, e.g. `RegisterAppInterfaceID -> RegisterAppInterface`. 2. The constant name should be camel case formatted. -0. The constant has 2 fields the first is the `int` value of the `"value"` attribute and the second is the `String` value of normalized `"name"` attribute. +3. The constant has 2 fields the first is the `int` value of the `"value"` attribute and the second is the `String` value of normalized `"name"` attribute. Internally it uses another file that lists all the function names `SDLRPCFunctionNames`. @@ -454,7 +444,7 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` -Each from MOBILE_API.XML is declares its function name in `SDLRPCFunctionNames.h` and `SDLRPCFunctionNames.m` files. +Each from MOBILE_API.XML declares its function name in `SDLRPCFunctionNames.h` and `SDLRPCFunctionNames.m` files. ```objc SDLRPCFunctionNames.h @@ -491,9 +481,6 @@ SDLRPCFunctionName const SDLRPCFunctionNameAddSubMenu = @"AddSubMenu"; ##### Request Functions (SDLRPCRequest) -##### Function with simple params - -This section depicts all functions which include one or a few parameters of the following types (integer|decimal|boolean|string). Such parameters are considered as simple. ```xml @@ -538,7 +525,6 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` - ```objc // SDLGetCloudAppProperties.m // @@ -577,7 +563,6 @@ NS_ASSUME_NONNULL_END ##### Response Functions (SDLRPCResponse) -##### Response class with extra argument example (Alert Response): ```xml @@ -615,7 +600,6 @@ NS_ASSUME_NONNULL_END ``` -As we can see it in the XML it requires an additional param `tryAgainTime` which is declared and implemented in the `SDLAlertResponse` class as follows: ```objc // SDLAlertResponse.h @@ -691,7 +675,6 @@ NS_ASSUME_NONNULL_END ##### Notification Functions (SDLRPCNotification) -Another example is the class `SDLOnAppInterfaceUnregistered`. It is declared in the XML as follows: ```xml @@ -701,8 +684,6 @@ Another example is the class `SDLOnAppInterfaceUnregistered`. It is declared in ``` -The corresponding Objective-C class `SDLOnAppInterfaceUnregistered` looks as following. - ```objc // SDLOnAppInterfaceUnregistered.h @@ -735,8 +716,6 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` -*Note: though the `SDLAppInterfaceUnregisteredReason reason` declared as a property but implemented as custom getter and setter* - ```objc // SDLOnAppInterfaceUnregistered.m From bbee519174c64e93af6a5e5f75cbe6d05f26d480 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 17 Mar 2020 10:39:25 +0100 Subject: [PATCH 12/28] Apply suggestions from code review update README.md Co-Authored-By: Joel Fischer --- generator/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/generator/README.md b/generator/README.md index 7f7989b7b..c44c8fb62 100644 --- a/generator/README.md +++ b/generator/README.md @@ -20,12 +20,6 @@ Please also make sure all git submodules are installed and up to date since the $ git submodule update --init --recursive ``` -or - -```shell script -$ git submodule update --recursive -``` - ## Usage **Usage example** From 4676ebdfec933116d3d47e8103da2677d1561675 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 17 Mar 2020 10:59:16 +0100 Subject: [PATCH 13/28] all enums / functions / structs will be generated --- generator/README.md | 6 +++--- generator/generator.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/generator/README.md b/generator/README.md index c44c8fb62..5b693c94a 100644 --- a/generator/README.md +++ b/generator/README.md @@ -56,9 +56,9 @@ optional arguments: only elements matched with defined regex pattern will be parsed and generated --verbose display additional details like logs etc - -e, --enums only specified elements will be generated, if present - -s, --structs only specified elements will be generated, if present - -m, -f, --functions only specified elements will be generated, if present + -e, --enums all enums will be generated, if present + -s, --structs all structs will be generated, if present + -m, -f, --functions all functions will be generated, if present -y, --overwrite force overwriting of existing files in output directory, ignore confirmation message -n, --skip skip overwriting of existing files in output diff --git a/generator/generator.py b/generator/generator.py index 85a27342b..01ccd193d 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -174,11 +174,11 @@ def get_parser(self): help='only elements matched with defined regex pattern will be parsed and generated') parser.add_argument('--verbose', action='store_true', help='display additional details like logs etc') parser.add_argument('-e', '--enums', required=False, action='store_true', - help='only specified elements will be generated, if present') + help='all enums will be generated, if present') parser.add_argument('-s', '--structs', required=False, action='store_true', - help='only specified elements will be generated, if present') + help='all structs will be generated, if present') parser.add_argument('-m', '-f', '--functions', required=False, action='store_true', - help='only specified elements will be generated, if present') + help='all functions will be generated, if present') parser.add_argument('-y', '--overwrite', action='store_true', help='force overwriting of existing files in output directory, ignore confirmation message') parser.add_argument('-n', '--skip', action='store_true', From 0f53ab92a5589ee857aa96104f6f7c34e319e4fc Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 17 Mar 2020 11:41:12 +0100 Subject: [PATCH 14/28] deprecated view update --- generator/templates/base_struct_function.h | 2 +- generator/templates/description.jinja | 3 --- generator/templates/enums/template.h | 2 +- generator/templates/enums/template_numeric.h | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h index c4ee1d142..c02ed3e7f 100644 --- a/generator/templates/base_struct_function.h +++ b/generator/templates/base_struct_function.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN {% include 'description.jinja' %} -@interface {{name}} : {{extends_class}}{{ending}} +@interface {{name}} : {{extends_class}}{{ending}}{{ " __deprecated" if deprecated and deprecated is sameas true }} {%- block constructors %} {% for c in constructors %} /** diff --git a/generator/templates/description.jinja b/generator/templates/description.jinja index 4eff1614e..0c8bd31c1 100644 --- a/generator/templates/description.jinja +++ b/generator/templates/description.jinja @@ -18,6 +18,3 @@ {%- endif %} */ {%- endif -%} -{%- if deprecated and deprecated is sameas true %} -__deprecated -{%- endif -%} diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index 7c7ec3eeb..0947017a7 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -7,7 +7,7 @@ {%- endblock -%} {%- block body %} {% include 'description.jinja' %} -typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; +typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}{{ " __deprecated" if deprecated and deprecated is sameas true }}; {% for param in params %} {% include 'description_param.jinja' %} extern {{ name }} const {{ name }}{{param.name}}{{ ' %s%s%s'|format('NS_SWIFT_NAME(', param.name|lower, ')') if NS_SWIFT_NAME is defined}}; diff --git a/generator/templates/enums/template_numeric.h b/generator/templates/enums/template_numeric.h index bfa24aa32..5167f3ab7 100644 --- a/generator/templates/enums/template_numeric.h +++ b/generator/templates/enums/template_numeric.h @@ -1,7 +1,7 @@ {% extends "template.h" %} {%- block body %} {% include 'description.jinja' %} -typedef NS_ENUM(NSUInteger, SDL{{ name }}){ +typedef NS_ENUM(NSUInteger, SDL{{ name }}){{ "__deprecated" if deprecated and deprecated is sameas true }}{ {% for param in params %} {%- macro someop() -%}{% include 'description_param.jinja' %}{%- endmacro -%} {{ someop()|indent(4, True) }} From 4b7197ee027bb8b437a427cfdf3bbca01c6cca02 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 17 Mar 2020 13:13:48 +0100 Subject: [PATCH 15/28] add SDLRPCParameterNames into readme.md --- generator/README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/generator/README.md b/generator/README.md index 5b693c94a..74c502d73 100644 --- a/generator/README.md +++ b/generator/README.md @@ -473,6 +473,40 @@ SDLRPCFunctionName const SDLRPCFunctionNameAddSubMenu = @"AddSubMenu"; . . . and so on ``` +Each from MOBILE_API.XML declares its parameter name in `SDLRPCParameterNames.h` and `SDLRPCParameterNames.m` files. + +```objc +// SDLRPCParameterNames.h + +#import +#import "SDLMacros.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* SDLRPCParameterName SDL_SWIFT_ENUM; + +extern SDLRPCParameterName const SDLRPCParameterNameAcEnable; +extern SDLRPCParameterName const SDLRPCParameterNameAcEnableAvailable; + +. . . and so on +``` + +And the implementation file SDLRPCParameterNames.m : + +```objc +// SDLRPCParameterNames.h + +#import "NSMutableDictionary+Store.h" +#import "SDLRPCParameterNames.h" + +NS_ASSUME_NONNULL_BEGIN + +SDLRPCParameterName const SDLRPCParameterNameAcEnable = @"acEnable"; +SDLRPCParameterName const SDLRPCParameterNameAcEnableAvailable = @"acEnableAvailable"; + +. . . and so on +``` + ##### Request Functions (SDLRPCRequest) From 88a2ae6d3ba1d92c3444893a7509078f5da1ac5e Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 17 Mar 2020 13:45:42 +0100 Subject: [PATCH 16/28] using NSNumber from not mandatory in init --- generator/transformers/common_producer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index c056d14f8..1d44956e5 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -142,7 +142,7 @@ def nullable(type_native, mandatory): :param mandatory: :return: """ - if mandatory or re.match(r'\w*Int\d*|BOOL|float|double', type_native): + if mandatory or re.match(r'BOOL|float|double', type_native): return '' return 'nullable ' @@ -153,8 +153,6 @@ def wrap(item): :param item: :return: """ - if getattr(item, 'constructor_argument_override', None) is not None: - return item.constructor_argument_override if re.match(r'\w*Int\d*|BOOL|float|double', item.type_native): return '@({})'.format(item.constructor_argument) return item.constructor_argument @@ -167,7 +165,6 @@ def extract_constructor(self, data: list, mandatory: bool) -> dict: :return: """ data = list(data) - # deprecated = any([m.deprecated for m in data if hasattr(m, 'deprecated')]) first = data.pop(0) init = ['{}:({}{}){}'.format(self.title(first.constructor_prefix), @@ -225,10 +222,11 @@ def extract_constructors(self, data: dict, designated_initializer: bool = None) return tuple(result) @staticmethod - def evaluate_type(instance) -> dict: + def evaluate_type(instance, mandatory) -> dict: """ :param instance: + :param mandatory: :return: """ data = OrderedDict() @@ -264,6 +262,8 @@ def evaluate_type(instance) -> dict: data['type_sdl'] = 'SDLUInt' data['of_class'] = 'NSNumber.class' data['type_sdl'] = 'NSNumber<{}> *'.format(data['type_sdl']) + if not mandatory: + data['type_native'] = data['type_sdl'] elif isinstance(instance, String): data['of_class'] = 'NSString.class' data['type_sdl'] = data['type_native'] = 'NSString *' @@ -281,11 +281,11 @@ def extract_type(self, param: Param) -> dict: """ if isinstance(param.param_type, Array): - data = self.evaluate_type(param.param_type.element_type) + data = self.evaluate_type(param.param_type.element_type, param.is_mandatory) data['for_name'] = data['for_name'] + 's' data['type_sdl'] = data['type_native'] = 'NSArray<{}> *'.format(data['type_sdl'].strip()) return data - return self.evaluate_type(param.param_type) + return self.evaluate_type(param.param_type, param.is_mandatory) @staticmethod def param_origin_change(name): From 45f8005b9c7bd32e72f8c0891e85feb55d688d93 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 17 Mar 2020 17:58:01 +0100 Subject: [PATCH 17/28] deprecated view update --- generator/generator.py | 5 ++++- generator/templates/base_struct_function.h | 2 +- generator/templates/description.jinja | 3 +++ generator/templates/enums/template.h | 4 ++-- generator/templates/enums/template_numeric.h | 4 ++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/generator/generator.py b/generator/generator.py index 01ccd193d..96901bbe4 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -12,11 +12,11 @@ from inspect import getfile from json import JSONDecodeError from os.path import basename, join +from pathlib import Path from re import findall from jinja2 import UndefinedError, TemplateNotFound, FileSystemLoader, Environment, ChoiceLoader, \ TemplateAssertionError, TemplateSyntaxError, TemplateRuntimeError -from pathlib import Path ROOT = Path(__file__).absolute().parents[0] @@ -317,6 +317,9 @@ async def process_main(self, skip, overwrite, items, transformer): """ tasks = [] for item in items.values(): + if item.name == 'FunctionID': + self.logger.warning('%s will be skipped', item.name) + continue render = transformer.transform(item) file = self.output_directory.joinpath(render.get('name', item.name)) for extension in ('.h', '.m'): diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h index c02ed3e7f..c4ee1d142 100644 --- a/generator/templates/base_struct_function.h +++ b/generator/templates/base_struct_function.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN {% include 'description.jinja' %} -@interface {{name}} : {{extends_class}}{{ending}}{{ " __deprecated" if deprecated and deprecated is sameas true }} +@interface {{name}} : {{extends_class}}{{ending}} {%- block constructors %} {% for c in constructors %} /** diff --git a/generator/templates/description.jinja b/generator/templates/description.jinja index 0c8bd31c1..4eff1614e 100644 --- a/generator/templates/description.jinja +++ b/generator/templates/description.jinja @@ -18,3 +18,6 @@ {%- endif %} */ {%- endif -%} +{%- if deprecated and deprecated is sameas true %} +__deprecated +{%- endif -%} diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index 0947017a7..d55f9f8ba 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -7,9 +7,9 @@ {%- endblock -%} {%- block body %} {% include 'description.jinja' %} -typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}{{ " __deprecated" if deprecated and deprecated is sameas true }}; +typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; {% for param in params %} {% include 'description_param.jinja' %} -extern {{ name }} const {{ name }}{{param.name}}{{ ' %s%s%s'|format('NS_SWIFT_NAME(', param.name|lower, ')') if NS_SWIFT_NAME is defined}}; +extern {{ name }} const {{ name }}{{param.name}}{{ " __deprecated" if param.deprecated and param.deprecated is sameas true }}; {% endfor -%} {% endblock -%} \ No newline at end of file diff --git a/generator/templates/enums/template_numeric.h b/generator/templates/enums/template_numeric.h index 5167f3ab7..a6493c6eb 100644 --- a/generator/templates/enums/template_numeric.h +++ b/generator/templates/enums/template_numeric.h @@ -1,11 +1,11 @@ {% extends "template.h" %} {%- block body %} {% include 'description.jinja' %} -typedef NS_ENUM(NSUInteger, SDL{{ name }}){{ "__deprecated" if deprecated and deprecated is sameas true }}{ +typedef NS_ENUM(NSUInteger, SDL{{ name }}){ {% for param in params %} {%- macro someop() -%}{% include 'description_param.jinja' %}{%- endmacro -%} {{ someop()|indent(4, True) }} - SDL{{ name }}{{ param.name }} = {{ param.value }}{{ ' %s%s%s'|format('NS_SWIFT_NAME(', param.name|lower, ')') if NS_SWIFT_NAME is defined}}; + SDL{{ name }}{{ param.name }} = {{ param.value }}{{ " __deprecated" if param.deprecated and param.deprecated is sameas true }}; {% endfor -%} }; {% endblock -%} \ No newline at end of file From 66e1fbab4c5c601b0c7b105aceed067bcd1b8abc Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Wed, 18 Mar 2020 14:33:06 +0100 Subject: [PATCH 18/28] Apply suggestions from code review Co-Authored-By: Joel Fischer --- generator/README.md | 12 ++++++------ generator/templates/SDLRPCFunctionNames.h | 3 ++- generator/templates/base_struct_function.h | 1 - generator/templates/base_struct_function.m | 1 - generator/templates/enums/template.h | 3 +-- generator/templates/enums/template.m | 3 +-- generator/templates/functions/template.m | 2 +- 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/generator/README.md b/generator/README.md index 74c502d73..216e730a7 100644 --- a/generator/README.md +++ b/generator/README.md @@ -2,11 +2,11 @@ This script provides the ability to auto-generate Objective-C RPC code (header \*.h and implementation \*.m classes) based on the SDL MOBILE_API XML specification. -## Requirements +## Requirements and Dependencies The script requires **Python 3** pre-installed on the host system. The minimal supported Python 3 version is **3.7.6**. It may work on versions back to 3.5 (the minimal version that has not yet reached [the end-of-life](https://devguide.python.org/devcycle/#end-of-life-branches)), but this is not supported and may break in the future. -Note: To install versions of Python 3, you must use the **pip3** command. +Note: To install the dependencies for this script, you must use the **pip3** command. All required libraries are listed in `requirements.txt` and should be pre-installed on the system prior to using the sript. Please use the following command to install the libraries: @@ -56,9 +56,9 @@ optional arguments: only elements matched with defined regex pattern will be parsed and generated --verbose display additional details like logs etc - -e, --enums all enums will be generated, if present - -s, --structs all structs will be generated, if present - -m, -f, --functions all functions will be generated, if present + -e, --enums if present, all enums will be generated + -s, --structs if present, all structs will be generated + -m, -f, --functions if present, all functions will be generated -y, --overwrite force overwriting of existing files in output directory, ignore confirmation message -n, --skip skip overwriting of existing files in output @@ -193,7 +193,7 @@ __deprecated extern SDLTouchType const SDLTouchTypeBegin; ``` -Take for an instance the enum class KeypressMode +Take, for instance, the enum class `KeypressMode`: ```xml diff --git a/generator/templates/SDLRPCFunctionNames.h b/generator/templates/SDLRPCFunctionNames.h index 18de36e12..39a6e27d8 100644 --- a/generator/templates/SDLRPCFunctionNames.h +++ b/generator/templates/SDLRPCFunctionNames.h @@ -8,6 +8,7 @@ */ typedef SDLEnum SDLRPCFunctionName SDL_SWIFT_ENUM; {% for param in params %} +# Create inline documentation for the parameter if there's a `since` variable and / or a description {%- if param.description or param.since %} /** {%- if param.description %} @@ -23,4 +24,4 @@ typedef SDLEnum SDLRPCFunctionName SDL_SWIFT_ENUM; */ {%- endif %} extern SDLRPCFunctionName const SDLRPCFunctionName{{ param.name }}; -{% endfor -%} \ No newline at end of file +{% endfor -%} diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h index c4ee1d142..75c157eb2 100644 --- a/generator/templates/base_struct_function.h +++ b/generator/templates/base_struct_function.h @@ -1,5 +1,4 @@ {% include 'copyright.txt' %} -// {{name}}.h {% block imports %} {%- for import in imports.enum %} #import "{{import}}.h" diff --git a/generator/templates/base_struct_function.m b/generator/templates/base_struct_function.m index addc1cb77..4e69b7745 100644 --- a/generator/templates/base_struct_function.m +++ b/generator/templates/base_struct_function.m @@ -1,5 +1,4 @@ {% include 'copyright.txt' %} -// {{name}}.m {%- block imports %} #import "{{name}}.h" #import "NSMutableDictionary+Store.h" diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index d55f9f8ba..0c98e6a00 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -1,5 +1,4 @@ {% include 'copyright.txt' %} -// {{ name }}.h {% block imports -%} {%- for import in imports %} #import "{{import}}.h" @@ -12,4 +11,4 @@ typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; {% include 'description_param.jinja' %} extern {{ name }} const {{ name }}{{param.name}}{{ " __deprecated" if param.deprecated and param.deprecated is sameas true }}; {% endfor -%} -{% endblock -%} \ No newline at end of file +{% endblock -%} diff --git a/generator/templates/enums/template.m b/generator/templates/enums/template.m index 7e669c293..5a83cb680 100644 --- a/generator/templates/enums/template.m +++ b/generator/templates/enums/template.m @@ -1,5 +1,4 @@ {% include 'copyright.txt' %} -// {{ name }}.m #import "{{name}}.h" {%- block body %} @@ -9,4 +8,4 @@ {%- for param in params %} {{ name }} const {{ name }}{{param.name}} = @"{{param.origin}}"; {%- endfor %} -{% endblock -%} \ No newline at end of file +{% endblock -%} diff --git a/generator/templates/functions/template.m b/generator/templates/functions/template.m index 07fb962be..3327a6495 100644 --- a/generator/templates/functions/template.m +++ b/generator/templates/functions/template.m @@ -1,8 +1,8 @@ {% extends "base_struct_function.m" %} {% block imports %} {{super()}} -#import "SDLRPCParameterNames.h" #import "SDLRPCFunctionNames.h" +#import "SDLRPCParameterNames.h" {%- endblock %} {% block constructors %} #pragma clang diagnostic push From a94c956876980df6680ffe78bd3f268351f39442 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Wed, 18 Mar 2020 20:39:00 +0100 Subject: [PATCH 19/28] Apply suggestions from code review --- generator/generator.py | 136 ++++++++------- generator/templates/base_struct_function.h | 2 + generator/templates/base_struct_function.m | 4 +- generator/templates/description.jinja | 3 +- generator/templates/description_param.jinja | 1 + generator/templates/enums/template.h | 3 +- generator/templates/enums/template_numeric.h | 10 +- generator/templates/functions/template.h | 1 + generator/templates/functions/template.m | 1 + generator/templates/structs/template.h | 1 + generator/templates/structs/template.m | 1 + generator/test/runner.py | 4 +- generator/test/test_CodeFormatAndQuality.py | 21 +-- generator/test/test_enums.py | 9 +- generator/test/test_functions.py | 131 ++++++++++---- generator/test/test_structs.py | 47 ++--- generator/transformers/common_producer.py | 173 ++++++++++--------- generator/transformers/enums_producer.py | 20 ++- generator/transformers/functions_producer.py | 63 ++++--- generator/transformers/generate_error.py | 12 -- generator/transformers/structs_producer.py | 12 +- 21 files changed, 371 insertions(+), 284 deletions(-) delete mode 100644 generator/transformers/generate_error.py diff --git a/generator/generator.py b/generator/generator.py index 96901bbe4..fbef269e7 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -41,7 +41,7 @@ class Generator: This class contains only technical features, as follow: - parsing command-line arguments, or evaluating required container interactively; - calling parsers to get Model from xml; - - calling producers to transform initial Model to dict used in jinja2 templates + - calling producers to transform initial Model to dict used in Jinja2 templates Not required to be covered by unit tests cause contains only technical features. """ @@ -53,20 +53,30 @@ def __init__(self): self.paths_named = namedtuple('paths_named', 'enum_class struct_class request_class response_class ' 'notification_class function_names parameter_names') + _version = '1.0.0' + @property - def output_directory(self) -> Path: + def get_version(self) -> str: + """ + version of the entire generator + :return: current entire generator version """ + return self._version - :return: + @property + def output_directory(self) -> Path: + """ + Getter for output directory + :return: output directory Path """ return self._output_directory @output_directory.setter - def output_directory(self, output_directory): + def output_directory(self, output_directory: str): """ - - :param output_directory: - :return: + Setting and validating output directory + :param output_directory: path to output directory + :return: None """ if output_directory.startswith('/'): path = Path(output_directory).absolute().resolve() @@ -84,17 +94,17 @@ def output_directory(self, output_directory): @property def env(self) -> Environment: """ - - :return: + Getter for Jinja2 instance + :return: initialized Jinja2 instance """ return self._env @env.setter - def env(self, paths): + def env(self, paths: list): """ - - :param paths: - :return: + Initiating Jinja2 instance + :param paths: list with paths to all Jinja2 templates + :return: None """ loaders = list(filter(lambda l: Path(l).exists(), paths)) if not loaders: @@ -107,27 +117,19 @@ def env(self, paths): self._env.globals['year'] = date.today().year @staticmethod - def title(name): - """ - - :param name: - :return: + def title(name: str): """ - return name[:1].upper() + name[1:] - - @property - def get_version(self): - """ - - :return: + Capitalizing only first character in string. Using for appropriate filter in Jinja2 + :param name: string to be capitalized first character + :return: initial parameter with capitalized first character """ - return Common.version + return Common.title(name) def config_logging(self, verbose): """ - - :param verbose: - :return: + Configuring logging for all application + :param verbose: if True setting logging.DEBUG else logging.ERROR + :return: None """ handler = logging.StreamHandler() handler.setFormatter(logging.Formatter(fmt='%(asctime)s.%(msecs)03d - %(levelname)s - %(message)s', @@ -147,8 +149,8 @@ def config_logging(self, verbose): def get_parser(self): """ - Parsing command-line arguments, or evaluating required container interactively. - :return: an instance of argparse.ArgumentParser + Parsing and evaluating command-line arguments + :return: object with parsed and validated CLI arguments """ if len(sys.argv) == 2 and sys.argv[1] in ('-v', '--version'): print(self.get_version) @@ -174,11 +176,11 @@ def get_parser(self): help='only elements matched with defined regex pattern will be parsed and generated') parser.add_argument('--verbose', action='store_true', help='display additional details like logs etc') parser.add_argument('-e', '--enums', required=False, action='store_true', - help='all enums will be generated, if present') + help='if present, all enums will be generated') parser.add_argument('-s', '--structs', required=False, action='store_true', - help='all structs will be generated, if present') + help='if present, all structs will be generated') parser.add_argument('-m', '-f', '--functions', required=False, action='store_true', - help='all functions will be generated, if present') + help='if present, all functions will be generated') parser.add_argument('-y', '--overwrite', action='store_true', help='force overwriting of existing files in output directory, ignore confirmation message') parser.add_argument('-n', '--skip', action='store_true', @@ -220,11 +222,11 @@ def get_parser(self): return args def versions_compatibility_validating(self): - """version of generator script requires the same or lesser version of parser script. + """ + Version of generator script requires the same or lesser version of parser script. if the parser script needs to fix a bug (and becomes, e.g. 1.0.1) and the generator script stays at 1.0.0. As long as the generator script is the same or greater major version, it should be parsable. This requires some level of backward compatibility. E.g. they have to be the same major version. - """ regex = r'(\d+\.\d+).(\d)' @@ -245,8 +247,9 @@ def versions_compatibility_validating(self): self.logger.info('Parser type: %s, version %s,\tGenerator version %s', basename(getfile(Parser().__class__)), parser_origin, generator_origin) - def get_paths(self, file_name=ROOT.joinpath('paths.ini')): + def get_paths(self, file_name: Path = ROOT.joinpath('paths.ini')): """ + Getting and validating parent classes names :param file_name: path to file with container :return: namedtuple with container to key elements """ @@ -277,7 +280,7 @@ def get_paths(self, file_name=ROOT.joinpath('paths.ini')): return self.paths_named(**data) - async def get_mappings(self, file=ROOT.joinpath('mapping.json')): + async def get_mappings(self, file: Path = ROOT.joinpath('mapping.json')): """ The key name in *.json is equal to property named in jinja2 templates :param file: path to file with manual mappings @@ -291,13 +294,13 @@ async def get_mappings(self, file=ROOT.joinpath('mapping.json')): self.logger.error('Failure to get mappings %s', error1) return OrderedDict() - def write_file(self, file, templates, data): + def write_file(self, file: Path, templates: list, data: dict): """ Calling producer/transformer instance to transform initial Model to dict used in jinja2 templates. Applying transformed dict to jinja2 templates and writing to appropriate file - :param file: output js file - :param templates: name of template - :param data: an instance of transformer for particular item + :param file: output file name + :param templates: list with templates + :param data: Dictionary with prepared output data, ready to be applied to Jinja2 template """ try: render = self.env.get_or_select_template(templates).render(data) @@ -307,7 +310,7 @@ def write_file(self, file, templates, data): as error1: self.logger.error('skipping %s, template not found %s', file.as_posix(), error1) - async def process_main(self, skip, overwrite, items, transformer): + async def process_main(self, skip: bool, overwrite: bool, items: dict, transformer): """ Process each item from initial Model. According to provided arguments skipping, overriding or asking what to to. :param skip: if file exist skip it @@ -333,14 +336,15 @@ async def process_main(self, skip, overwrite, items, transformer): await asyncio.gather(*tasks) - async def process_function_name(self, skip, overwrite, functions, structs, transformer): + async def process_function_name(self, skip: bool, overwrite: bool, functions: dict, structs: dict, + transformer: FunctionsProducer): """ - - :param skip: - :param overwrite: - :param functions: - :param structs: - :param transformer: + Processing output for SDLRPCFunctionNames and SDLRPCParameterNames + :param skip: if target file exist it will be skipped + :param overwrite: if target file exist it will be overwritten + :param functions: Dictionary with all functions + :param structs: Dictionary with all structs + :param transformer: FunctionsProducer (transformer) instance :return: """ tasks = [] @@ -360,15 +364,15 @@ async def process_function_name(self, skip, overwrite, functions, structs, trans await asyncio.gather(*tasks) - async def process_common(self, skip, overwrite, file_with_suffix, data, templates): + async def process_common(self, skip: bool, overwrite: bool, file_with_suffix: Path, data: dict, templates: list): """ - - :param skip: - :param overwrite: - :param file_with_suffix: - :param data: - :param templates: - :return: + Processing output common + :param skip: if target file exist it will be skipped + :param overwrite: if target file exist it will be overwritten + :param file_with_suffix: output file name + :param data: Dictionary with prepared output data, ready to be applied to Jinja2 template + :param templates: list with paths to Jinja2 templates + :return: None """ if file_with_suffix.is_file(): if skip: @@ -400,10 +404,10 @@ async def process_common(self, skip, overwrite, file_with_suffix, data, template @staticmethod def filter_pattern(interface, pattern): """ - - :param interface: - :param pattern: - :return: + Filtering Model to match with regex pattern + :param interface: Initial (original) Model, obtained from module 'rpc_spec/InterfaceParser' + :param pattern: regex pattern (string) + :return: Model with items which match with regex pattern """ names = tuple(interface.enums.keys()) + tuple(interface.structs.keys()) @@ -419,10 +423,10 @@ def filter_pattern(interface, pattern): async def parser(self, source_xml, source_xsd): """ - - :param source_xml: - :param source_xsd: - :return: + Getting Model from source_xml, parsed and validated by module 'rpc_spec/InterfaceParser' + :param source_xml: path to xml file + :param source_xsd: path to xsd file + :return: Model, obtained from module 'rpc_spec/InterfaceParser' """ try: start = datetime.now() diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h index 75c157eb2..57fb1a6de 100644 --- a/generator/templates/base_struct_function.h +++ b/generator/templates/base_struct_function.h @@ -1,3 +1,5 @@ +{#- To avoid code duplication was crated this parent file, which contain common part used in: + "templates/functions/template.h" and "templates/structs/template.h". -#} {% include 'copyright.txt' %} {% block imports %} {%- for import in imports.enum %} diff --git a/generator/templates/base_struct_function.m b/generator/templates/base_struct_function.m index 4e69b7745..610a31d90 100644 --- a/generator/templates/base_struct_function.m +++ b/generator/templates/base_struct_function.m @@ -1,3 +1,5 @@ +{#- To avoid code duplication was crated this parent file, which contain common part used in: + "templates/functions/template.m" and "templates/structs/template.m". -#} {% include 'copyright.txt' %} {%- block imports %} #import "{{name}}.h" @@ -17,7 +19,7 @@ - (instancetype)initWith{{c.init}} { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} - self = [{{'self' if c.self else 'super'}} init{{'With'+c.self if c.self}}]; + self = [{{ 'self' if c.self else 'super' }} init{{ 'With' + c.self if c.self and c.self is string }}]; {%- if c.deprecated %} #pragma clang diagnostic pop {%- endif %} diff --git a/generator/templates/description.jinja b/generator/templates/description.jinja index 4eff1614e..fb4932270 100644 --- a/generator/templates/description.jinja +++ b/generator/templates/description.jinja @@ -1,3 +1,4 @@ +{#- Content of this file include into every item (Enum/Struct/Function) between imports and typedef/@interface declaration -#} {% if description or since or history %} /** {%- if description %} @@ -18,6 +19,6 @@ {%- endif %} */ {%- endif -%} -{%- if deprecated and deprecated is sameas true %} +{%- if deprecated %} __deprecated {%- endif -%} diff --git a/generator/templates/description_param.jinja b/generator/templates/description_param.jinja index c2926a1f1..cb0cc4244 100644 --- a/generator/templates/description_param.jinja +++ b/generator/templates/description_param.jinja @@ -1,3 +1,4 @@ +{#- Content of this file include into every Param/sub-element of (Enum/Struct/Function) -#} {% if param.description or param.since or param.history %} /** {%- if param.description %} diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index 0c98e6a00..033626c1c 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -1,3 +1,4 @@ +{#- String based enum. If the enum element doesn't have values it going to be string enum -#} {% include 'copyright.txt' %} {% block imports -%} {%- for import in imports %} @@ -8,7 +9,7 @@ {% include 'description.jinja' %} typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; {% for param in params %} -{% include 'description_param.jinja' %} +{%- include 'description_param.jinja' %} extern {{ name }} const {{ name }}{{param.name}}{{ " __deprecated" if param.deprecated and param.deprecated is sameas true }}; {% endfor -%} {% endblock -%} diff --git a/generator/templates/enums/template_numeric.h b/generator/templates/enums/template_numeric.h index a6493c6eb..f16e8f834 100644 --- a/generator/templates/enums/template_numeric.h +++ b/generator/templates/enums/template_numeric.h @@ -1,11 +1,13 @@ +{#- Numeric based enum. If the enum element values are numeric the enum should be a numerical. + This template extends base "templates/enums/template.h" and overriding block body -#} {% extends "template.h" %} {%- block body %} {% include 'description.jinja' %} -typedef NS_ENUM(NSUInteger, SDL{{ name }}){ +typedef NS_ENUM(NSUInteger, {{ name }}){ {% for param in params %} -{%- macro someop() -%}{% include 'description_param.jinja' %}{%- endmacro -%} -{{ someop()|indent(4, True) }} - SDL{{ name }}{{ param.name }} = {{ param.value }}{{ " __deprecated" if param.deprecated and param.deprecated is sameas true }}; +{%- macro description_param() -%}{% include 'description_param.jinja' %}{%- endmacro -%} +{{ description_param()|indent(4, True) }} + {{ name }}{{ param.name }} = {{ param.value }}{{ " __deprecated" if param.deprecated }}{{',' if not loop.last }} {% endfor -%} }; {% endblock -%} \ No newline at end of file diff --git a/generator/templates/functions/template.h b/generator/templates/functions/template.h index 51f426c26..b2301dba3 100644 --- a/generator/templates/functions/template.h +++ b/generator/templates/functions/template.h @@ -1 +1,2 @@ +{#- This template creates RPC requests, responses, and notification .h files -#} {% extends "base_struct_function.h" %} \ No newline at end of file diff --git a/generator/templates/functions/template.m b/generator/templates/functions/template.m index 3327a6495..6b23240ae 100644 --- a/generator/templates/functions/template.m +++ b/generator/templates/functions/template.m @@ -1,3 +1,4 @@ +{#- This template creates RPC requests, responses, and notification .m files -#} {% extends "base_struct_function.m" %} {% block imports %} {{super()}} diff --git a/generator/templates/structs/template.h b/generator/templates/structs/template.h index 51f426c26..af44cb710 100644 --- a/generator/templates/structs/template.h +++ b/generator/templates/structs/template.h @@ -1 +1,2 @@ +{#- This template creates RPC struct .h files -#} {% extends "base_struct_function.h" %} \ No newline at end of file diff --git a/generator/templates/structs/template.m b/generator/templates/structs/template.m index c81f652cf..0d4c1b760 100644 --- a/generator/templates/structs/template.m +++ b/generator/templates/structs/template.m @@ -1,3 +1,4 @@ +{#- This template creates RPC struct .m files -#} {% extends "base_struct_function.m" %} {% block imports %} {{super()}} diff --git a/generator/test/runner.py b/generator/test/runner.py index 5268b3bb4..ac72c6a2e 100644 --- a/generator/test/runner.py +++ b/generator/test/runner.py @@ -23,7 +23,7 @@ def config_logging(): """ - :return: None + Configuring logging for all application """ handler = logging.StreamHandler() handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -36,7 +36,7 @@ def config_logging(): def main(): """ - :return: None + Entry point for parser and generator """ config_logging() suite = TestSuite() diff --git a/generator/test/test_CodeFormatAndQuality.py b/generator/test/test_CodeFormatAndQuality.py index b512823c8..c9b34e06f 100755 --- a/generator/test/test_CodeFormatAndQuality.py +++ b/generator/test/test_CodeFormatAndQuality.py @@ -1,7 +1,6 @@ -"""Interface model unit test - """ -import subprocess +Interface model unit test +""" import unittest from os import walk from os.path import join @@ -12,28 +11,22 @@ class CodeFormatAndQuality(unittest.TestCase): def setUp(self): - """Searching for all python files to be checked - - """ self.list_of_files = [] for (directory, _, filenames) in walk(Path(__file__).absolute().parents[1].as_posix()): self.list_of_files += [join(directory, file) for file in filenames if file.endswith('.py') and 'test' not in directory and 'rpc_spec' not in directory] - def test_check(self): - """Performing checks by flake8 - + def test_checkCodeFormatAndQuality(self): + """ + Performing checks of Code Format And Quality by flake8 + If any inconvenient low quality code will be found, this will be shown in stdout and + each such cases will be reflected with report.total_errors number """ - style_guide = flake8.get_style_guide(max_line_length=120) report = style_guide.check_files(self.list_of_files) self.assertEqual(report.total_errors, 0) - process = subprocess.Popen(["flake8", '--exclude=rpc_spec,test', '..'], stdout=subprocess.PIPE) - (output, err) = process.communicate() - print(output.decode("utf-8")) - if __name__ == '__main__': unittest.main() diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py index d18a6c2ac..b7205e984 100644 --- a/generator/test/test_enums.py +++ b/generator/test/test_enums.py @@ -1,4 +1,4 @@ -from collections import OrderedDict, defaultdict +from collections import OrderedDict from unittest import TestCase from model.enum import Enum @@ -7,6 +7,13 @@ class TestEnumsProducer(TestCase): + """ + The structures of tests in this class was prepared to cover all possible combinations of code branching in tested + class EnumsProducer. + All names of Enums and nested elements doesn't reflating with real Enums + and could be replaces with some meaningless names. + """ + def setUp(self): self.maxDiff = None diff --git a/generator/test/test_functions.py b/generator/test/test_functions.py index 8b9867bc6..a481317c3 100644 --- a/generator/test/test_functions.py +++ b/generator/test/test_functions.py @@ -1,5 +1,5 @@ import re -from collections import namedtuple, OrderedDict, defaultdict +from collections import namedtuple, OrderedDict from unittest import TestCase from model.array import Array @@ -16,6 +16,13 @@ class TestFunctionsProducer(TestCase): + """ + The structures of tests in this class was prepared to cover all possible combinations of code branching in tested + class FunctionsProducer. + All names of Functions and nested elements doesn't reflating with real Enums + and could be replaces with some meaningless names. + """ + def setUp(self): self.maxDiff = None @@ -68,12 +75,13 @@ def test_process_function_name(self): origin='availableHdChannelsAvailable'), self.producer.common_names(description=[], name='Ignore', origin='ignore', since=None), self.producer.common_names(description=['image description'], name='Image', origin='image', since='1.0.0'), - self.producer.common_names(description=[], name='PresetBankCapabilities', origin='PresetBankCapabilities', since=None), + self.producer.common_names(description=[], name='PresetBankCapabilities', origin='PresetBankCapabilities', + since=None), self.producer.common_names(description=[], name='SoftButton', origin='SoftButton', since=None), - self.producer.common_names(description=['syncMsgVersion description'], name='SyncMsgVersion', + self.producer.common_names(description=['syncMsgVersion description'], name='SdlMsgVersion', origin='syncMsgVersion', since='3.5.0')] actual = self.producer.get_simple_params(functions, structs) - self.assertListEqual(expected, actual['params']) + self.assertCountEqual(expected, actual['params']) def test_RegisterAppInterfaceRequest(self): params = OrderedDict() @@ -102,18 +110,18 @@ def test_RegisterAppInterfaceRequest(self): expected['name'] = 'SDLRegisterAppInterface' expected['extends_class'] = 'SDLRPCRequest' expected['imports'] = { - '.h': {'enum': {'SDLRPCRequest'}, 'struct': {'SDLTemplateColorScheme', 'SDLTTSChunk', 'SDLSyncMsgVersion'}}, - '.m': {'SDLTemplateColorScheme', 'SDLTTSChunk', 'SDLSyncMsgVersion'}} + '.h': {'enum': {'SDLRPCRequest'}, 'struct': {'SDLTemplateColorScheme', 'SDLTTSChunk', 'SDLSdlMsgVersion'}}, + '.m': {'SDLTemplateColorScheme', 'SDLTTSChunk', 'SDLSdlMsgVersion'}} expected['description'] = ['Establishes an interface with a mobile application. Before registerAppInterface no ' 'other commands will be', 'accepted/executed.'] expected['since'] = '1.0.0' expected['params'] = ( self.producer.param_named( - constructor_argument='syncMsgVersion', constructor_argument_override=None, - constructor_prefix='SyncMsgVersion', deprecated=False, description=['See SyncMsgVersion'], - for_name='object', mandatory=True, method_suffix='SyncMsgVersion', modifier='strong', - of_class='SDLSyncMsgVersion.class', origin='syncMsgVersion', since=None, - type_native='SDLSyncMsgVersion *', type_sdl='SDLSyncMsgVersion *'), + constructor_argument='sdlMsgVersion', constructor_argument_override=None, + constructor_prefix='SdlMsgVersion', deprecated=False, description=['See SyncMsgVersion'], + for_name='object', mandatory=True, method_suffix='SdlMsgVersion', modifier='strong', + of_class='SDLSdlMsgVersion.class', origin='sdlMsgVersion', since=None, + type_native='SDLSdlMsgVersion *', type_sdl='SDLSdlMsgVersion *'), self.producer.param_named( constructor_argument='fullAppID', constructor_argument_override=None, constructor_prefix='FullAppID', deprecated=False, description=['ID used', @@ -140,8 +148,8 @@ def test_RegisterAppInterfaceRequest(self): origin='isMediaApplication', since=None, type_native='BOOL', type_sdl='NSNumber *')) mandatory_arguments = [ - self.producer.argument_named(variable='syncMsgVersion', deprecated=False, origin='syncMsgVersion', - constructor_argument='syncMsgVersion'), + self.producer.argument_named(variable='sdlMsgVersion', deprecated=False, origin='sdlMsgVersion', + constructor_argument='sdlMsgVersion'), self.producer.argument_named(variable='isMediaApplication', deprecated=False, origin='isMediaApplication', constructor_argument='@(isMediaApplication)')] not_mandatory_arguments = [ @@ -151,13 +159,13 @@ def test_RegisterAppInterfaceRequest(self): constructor_argument='dayColorScheme'), self.producer.argument_named(variable='ttsName', deprecated=False, origin='ttsName', constructor_argument='ttsName')] - mandatory_init = 'SyncMsgVersion:(SDLSyncMsgVersion *)syncMsgVersion ' \ + mandatory_init = 'SdlMsgVersion:(SDLSdlMsgVersion *)sdlMsgVersion ' \ 'isMediaApplication:(BOOL)isMediaApplication' expected['constructors'] = ( self.producer.constructor_named( all=mandatory_arguments, arguments=mandatory_arguments, deprecated=False, - init=mandatory_init, self=''), + init=mandatory_init, self=True), self.producer.constructor_named( all=mandatory_arguments + not_mandatory_arguments, arguments=not_mandatory_arguments, deprecated=False, init=mandatory_init + ' fullAppID:(nullable NSString *)fullAppID dayColorScheme:(nullable ' @@ -253,7 +261,7 @@ def test_OnHMIStatus(self): constructor_argument='hmiLevel')] expected['constructors'] = (self.producer.constructor_named( - all=arguments, arguments=arguments, deprecated=False, self='', init='HmiLevel:(SDLHMILevel)hmiLevel'),) + all=arguments, arguments=arguments, deprecated=False, self=True, init='HmiLevel:(SDLHMILevel)hmiLevel'),) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) @@ -265,6 +273,8 @@ def test_CreateWindow(self): params['position'] = Param(name='position', param_type=Integer(default_value=1000, max_value=1000, min_value=0)) params['speed'] = Param(name='speed', param_type=Float(max_value=700.0, min_value=0.0)) params['offset'] = Param(name='offset', param_type=Integer(max_value=100000000000, min_value=0)) + params['duplicateUpdatesFromWindowID'] = Param(name='duplicateUpdatesFromWindowID', param_type=Integer(), + is_mandatory=False) item = Function(name='CreateWindow', function_id=EnumElement(name='CreateWindowID'), message_type=EnumElement(name='request'), params=params) @@ -300,23 +310,41 @@ def test_CreateWindow(self): constructor_argument='offset', constructor_argument_override=None, constructor_prefix='Offset', deprecated=False, description=['{"default_value": null, "max_value": 100000000000, "min_value": 0}'], for_name='object', mandatory=True, method_suffix='Offset', modifier='strong', of_class='NSNumber.class', - origin='offset', since=None, type_native='UInt64', type_sdl='NSNumber *')) - - expected_arguments = [self.producer.argument_named(variable='windowID', deprecated=False, origin='windowID', - constructor_argument='@(windowID)'), - self.producer.argument_named(variable='cmdID', deprecated=False, origin='cmdID', - constructor_argument='@(cmdID)'), - self.producer.argument_named(variable='position', deprecated=False, origin='position', - constructor_argument='@(position)'), - self.producer.argument_named(variable='speed', deprecated=False, origin='speed', - constructor_argument='@(speed)'), - self.producer.argument_named(variable='offset', deprecated=False, origin='offset', - constructor_argument='@(offset)')] + origin='offset', since=None, type_native='UInt64', type_sdl='NSNumber *'), + self.producer.param_named( + constructor_argument='duplicateUpdatesFromWindowID', constructor_argument_override=None, + constructor_prefix='DuplicateUpdatesFromWindowID', deprecated=False, + description=['{"default_value": null, "max_value": null, "min_value": null}'], for_name='object', + mandatory=False, method_suffix='DuplicateUpdatesFromWindowID', modifier='strong', + of_class='NSNumber.class', origin='duplicateUpdatesFromWindowID', since=None, + type_native='NSNumber *', type_sdl='NSNumber *')) - expected['constructors'] = (self.producer.constructor_named( - all=expected_arguments, arguments=expected_arguments, deprecated=False, self='', - init='WindowID:(UInt32)windowID cmdID:(UInt32)cmdID position:(UInt16)position speed:(float)speed ' - 'offset:(UInt64)offset'),) + not_mandatory_arguments = [ + self.producer.argument_named(variable='windowID', deprecated=False, origin='windowID', + constructor_argument='@(windowID)'), + self.producer.argument_named(variable='cmdID', deprecated=False, origin='cmdID', + constructor_argument='@(cmdID)'), + self.producer.argument_named(variable='position', deprecated=False, origin='position', + constructor_argument='@(position)'), + self.producer.argument_named(variable='speed', deprecated=False, origin='speed', + constructor_argument='@(speed)'), + self.producer.argument_named(variable='offset', deprecated=False, origin='offset', + constructor_argument='@(offset)')] + mandatory_arguments = [self.producer.argument_named( + variable='duplicateUpdatesFromWindowID', deprecated=False, origin='duplicateUpdatesFromWindowID', + constructor_argument='duplicateUpdatesFromWindowID')] + + expected['constructors'] = ( + self.producer.constructor_named( + all=not_mandatory_arguments, arguments=not_mandatory_arguments, deprecated=False, self=True, + init='WindowID:(UInt32)windowID cmdID:(UInt32)cmdID position:(UInt16)position speed:(float)speed ' + 'offset:(UInt64)offset'), + self.producer.constructor_named( + all=not_mandatory_arguments + mandatory_arguments, arguments=mandatory_arguments, + deprecated=False, self='WindowID:windowID cmdID:cmdID position:position speed:speed offset:offset', + init='WindowID:(UInt32)windowID cmdID:(UInt32)cmdID position:(UInt16)position speed:(float)speed ' + 'offset:(UInt64)offset duplicateUpdatesFromWindowID:(nullable NSNumber *)' + 'duplicateUpdatesFromWindowID')) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) @@ -344,8 +372,45 @@ def test_CreateInteractionChoiceSet(self): origin='choiceSet')] expected['constructors'] = (self.producer.constructor_named( - all=argument, arguments=argument, deprecated=False, self='', + all=argument, arguments=argument, deprecated=False, self=True, init='ChoiceSet:(NSArray *)choiceSet'),) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) + + def test_SetDisplayLayout(self): + params = OrderedDict() + params['displayLayout'] = Param(name='displayLayout', param_type=String(max_length=500, min_length=1)) + item = Function(name='SetDisplayLayout', function_id=EnumElement(name='SetDisplayLayoutID'), + message_type=EnumElement(name='request'), params=params, history=[ + Function(name='SetDisplayLayout', function_id=EnumElement(name='SetDisplayLayoutID'), + message_type=EnumElement(name='request'), params={}, history=None, since='3.0.0', + until='6.0.0') + ], since='6.0.0', until=None, deprecated='true') + + expected = OrderedDict() + expected['origin'] = 'SetDisplayLayout' + expected['name'] = 'SDLSetDisplayLayout' + expected['extends_class'] = 'SDLRPCRequest' + expected['imports'] = {'.h': {'enum': {'SDLRPCRequest'}, 'struct': set()}, '.m': set()} + expected['since'] = '6.0.0' + expected['history'] = '3.0.0' + expected['deprecated'] = True + expected['params'] = ( + self.producer.param_named( + constructor_argument='displayLayout', constructor_argument_override=None, + constructor_prefix='DisplayLayout', deprecated=False, + description=['{"default_value": null, "max_length": 500, "min_length": 1}'], for_name='object', + mandatory=True, method_suffix='DisplayLayout', modifier='strong', of_class='NSString.class', + origin='displayLayout', since=None, type_native='NSString *', type_sdl='NSString *'),) + + argument = [ + self.producer.argument_named(variable='displayLayout', deprecated=False, + constructor_argument='displayLayout', origin='displayLayout')] + + expected['constructors'] = (self.producer.constructor_named( + all=argument, arguments=argument, deprecated=False, self=True, + init='DisplayLayout:(NSString *)displayLayout'),) + + actual = self.producer.transform(item) + self.assertDictEqual(expected, actual) diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py index 9fc65b476..77186799e 100644 --- a/generator/test/test_structs.py +++ b/generator/test/test_structs.py @@ -1,7 +1,7 @@ -import re -from collections import OrderedDict, defaultdict +from collections import OrderedDict from unittest import TestCase +from model.integer import Integer from model.param import Param from model.string import String from model.struct import Struct @@ -9,16 +9,18 @@ class TestStructsProducer(TestCase): + """ + The structures of tests in this class was prepared to cover all possible combinations of code branching in tested + class StructsProducer. + All names of Structs and nested elements doesn't reflating with real Enums + and could be replaces with some meaningless names. + """ + def setUp(self): self.maxDiff = None self.producer = StructsProducer('SDLRPCStruct', ['Image']) - def test_Version(self): - version = self.producer.get_version - self.assertIsNotNone(version) - self.assertTrue(re.match(r'^\d*\.\d*\.\d*$', version)) - def test_CloudAppProperties(self): item = Struct(name='CloudAppProperties', members={ 'appID': Param(name='appID', param_type=String()) @@ -33,10 +35,11 @@ def test_CloudAppProperties(self): constructor_argument='appID', constructor_argument_override=None, constructor_prefix='AppID', deprecated=False, description=['{"default_value": null, "max_length": null, "min_length": null}'], for_name='object', mandatory=True, method_suffix='AppID', modifier='strong', of_class='NSString.class', - origin='appID', since=None, type_native='NSString *', type_sdl='NSString *', ),) + origin='appID', since=None, type_native='NSString *', type_sdl='NSString *'),) argument = [ - self.producer.argument_named(variable='appID', deprecated=False, constructor_argument='appID', origin='appID')] + self.producer.argument_named(variable='appID', deprecated=False, constructor_argument='appID', + origin='appID')] expected['constructors'] = (self.producer.constructor_named( all=argument, arguments=argument, deprecated=False, self='', @@ -45,28 +48,30 @@ def test_CloudAppProperties(self): actual = self.producer.transform(item) self.assertDictEqual(expected, actual) - def test_not_mandatory_NS_DESIGNATED_INITIALIZER(self): - item = Struct(name='CloudAppProperties', members={ - 'appID': Param(name='appID', param_type=String(), is_mandatory=False) + def test_TouchEvent(self): + item = Struct(name='TouchEvent', members={ + 'id': Param(name='id', param_type=Integer(max_value=9, min_value=0)) }) expected = OrderedDict() - expected['origin'] = 'CloudAppProperties' - expected['name'] = 'SDLCloudAppProperties' + expected['origin'] = 'TouchEvent' + expected['name'] = 'SDLTouchEvent' expected['extends_class'] = 'SDLRPCStruct' - expected['imports'] = {'.m': set(), '.h': {'enum': {'SDLRPCStruct'}, 'struct': set()}} + expected['imports'] = {'.h': {'enum': {'SDLRPCStruct'}, 'struct': set()}, '.m': set()} expected['params'] = ( self.producer.param_named( - constructor_argument='appID', constructor_argument_override=None, constructor_prefix='AppID', - deprecated=False, description=['{"default_value": null, "max_length": null, "min_length": null}'], - for_name='object', mandatory=False, method_suffix='AppID', modifier='strong', of_class='NSString.class', - origin='appID', since=None, type_native='NSString *', type_sdl='NSString *', ),) + constructor_argument='touchEventId', constructor_argument_override=None, + constructor_prefix='TouchEventId', deprecated=False, + description=['{"default_value": null, "max_value": 9, "min_value": 0}'], for_name='object', + mandatory=True, method_suffix='TouchEventId', modifier='strong', of_class='NSNumber.class', + origin='touchEventId', since=None, type_native='UInt8', type_sdl='NSNumber *'),) argument = [ - self.producer.argument_named(variable='appID', deprecated=False, constructor_argument='appID', origin='appID')] + self.producer.argument_named(variable='touchEventId', deprecated=False, + constructor_argument='@(touchEventId)', origin='touchEventId')] expected['constructors'] = (self.producer.constructor_named( all=argument, arguments=argument, deprecated=False, self='', - init='AppID:(nullable NSString *)appID'),) + init='TouchEventId:(UInt8)touchEventId'),) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index 1d44956e5..3b2adab76 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -1,5 +1,5 @@ """ -Common transformer +All Enums/Structs/Functions Producer are inherited from this class and using features of it """ import json import logging @@ -21,15 +21,13 @@ class InterfaceProducerCommon(ABC): """ - Common transformer + All Enums/Structs/Functions Producer are inherited from this class and using features of it """ - version = '1.0.0' - def __init__(self, container_name, names=()): self.logger = logging.getLogger(self.__class__.__name__) self.container_name = container_name - self.names = names + self.names = list(map(lambda e: self.replace_sync(e), names)) self.param_named = namedtuple('param_named', 'origin constructor_argument constructor_prefix deprecated mandatory since ' 'method_suffix of_class type_native type_sdl modifier for_name description ' @@ -37,20 +35,13 @@ def __init__(self, container_name, names=()): self.constructor_named = namedtuple('constructor', 'init self arguments all deprecated') self.argument_named = namedtuple('argument', 'origin constructor_argument variable deprecated') - @property - def get_version(self): - """ - - :return: - """ - return self.version - def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: """ - - :param item: - :param render: - :return: + Main entry point for transforming each Enum/Function/Struct into output dictionary, + which going to be applied to Jinja2 template + :param item: instance of Enum/Function/Struct + :param render: dictionary with pre filled entries, which going to be filled/changed by reference + :return: dictionary which going to be applied to Jinja2 template """ if item.description: render['description'] = self.extract_description(item.description) @@ -58,65 +49,77 @@ def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: render['since'] = item.since if item.history: render['history'] = item.history.pop().since - if item.deprecated and item.deprecated.lower() == 'true': + if item.deprecated and str(item.deprecated).lower() == 'true': render['deprecated'] = True render['params'] = OrderedDict() for param in getattr(item, self.container_name).values(): - if param.name.lower() in ['id']: + param.name = self.replace_sync(param.name) + if param.name.lower() == 'id': param.name = self.minimize_first(item.name) + self.title(param.name) render['params'][param.name] = self.extract_param(param) if isinstance(item, (Struct, Function)): self.extract_imports(param, render['imports']) if 'constructors' not in render and isinstance(item, (Struct, Function)): - designated_initializer = render['designated_initializer'] if 'designated_initializer' in render else False - render['constructors'] = self.extract_constructors(render['params'], designated_initializer) + render['constructors'] = self.extract_constructors(render['params']) render['params'] = tuple(render['params'].values()) return render - def extract_imports(self, param: Param, imports): + @staticmethod + def replace_sync(name: str = '') -> str: """ - - :param param: - :param imports: - :return: + :param name: string with item name + :return: string with replaced 'sync' to 'sdl' """ + return re.sub(r'^([sS])ync(.+)$', r'\1dl\2', name) - def evaluate(element): - if isinstance(element, (Struct, Enum)): - return element.name, type(element).__name__.lower() - return None, None + def extract_imports(self, param: Param, imports: dict): + """ + Extracting appropriate imports and updating in render['imports'] by reference + :param param: instance of Param, which is sub element of Enum/Function/Struct + :param imports: dictionary from render['imports'] + :return: dictionary with extracted imports + """ if isinstance(param.param_type, Array): - type_origin, kind = evaluate(param.param_type.element_type) + type_origin, kind = self.evaluate_import(param.param_type.element_type) else: - type_origin, kind = evaluate(param.param_type) + type_origin, kind = self.evaluate_import(param.param_type) - if type_origin in self.names: + if type_origin and (type_origin in self.names or self.title(type_origin) in self.names): name = 'SDL' + type_origin imports['.h'][kind].add(name) imports['.m'].add(name) return imports - @staticmethod - def title(name): + def evaluate_import(self, element): + """ + :param element: instance of param.param_type + :return: tuple with element.name, type(element).__name__.lower() """ + if isinstance(element, (Struct, Enum)): + return self.replace_sync(element.name), type(element).__name__.lower() + return None, None - :param name: - :return: + @staticmethod + def title(name: str = '') -> str: + """ + Capitalizing only first character in string. + :param name: string to be capitalized first character + :return: initial parameter with capitalized first character """ return name[:1].upper() + name[1:] @staticmethod - def minimize_first(name): + def minimize_first(name: str = '') -> str: """ - - :param name: - :return: + Minimizing only first character in string. + :param name: string to be minimized first character + :return: initial parameter with minimized first character """ return name[:1].lower() + name[1:] @@ -125,7 +128,7 @@ def extract_description(data, length: int = 113) -> list: """ Evaluate, align and delete @TODO :param data: list with description - :param length: + :param length: length of the string to be split :return: evaluated string """ if not data: @@ -135,12 +138,12 @@ def extract_description(data, length: int = 113) -> list: return textwrap.wrap(re.sub(r'(\s{2,}|\n|\[@TODO.+)', ' ', data).strip(), length) @staticmethod - def nullable(type_native, mandatory): + def nullable(type_native: str, mandatory: bool) -> str: """ - - :param type_native: - :param mandatory: - :return: + Used for adding nullable modificator into initiator (constructor) parameter + :param type_native: native type + :param mandatory: is parameter mandatory + :return: string with modificator """ if mandatory or re.match(r'BOOL|float|double', type_native): return '' @@ -149,9 +152,9 @@ def nullable(type_native, mandatory): @staticmethod def wrap(item): """ - - :param item: - :return: + Used for wrapping appropriate initiator (constructor) parameter with '@({})' + :param item: named tup[le with initiator (constructor) parameter + :return: wrapped parameter """ if re.match(r'\w*Int\d*|BOOL|float|double', item.type_native): return '@({})'.format(item.constructor_argument) @@ -159,10 +162,10 @@ def wrap(item): def extract_constructor(self, data: list, mandatory: bool) -> dict: """ - - :param data: - :param mandatory: - :return: + Preparing dictionary with initial initiator (constructor) + :param data: list with prepared parameters + :param mandatory: is parameter mandatory + :return: dictionary with initial initiator (constructor) """ data = list(data) @@ -179,15 +182,14 @@ def extract_constructor(self, data: list, mandatory: bool) -> dict: self.nullable(param.type_native, mandatory), param.type_native.strip(), param.constructor_argument) init.append(init_str) + _self = True if 'functions' in self.__class__.__name__.lower() and mandatory else '' + return {'init': ' '.join(init), 'self': _self, 'arguments': arguments, 'all': arguments, 'deprecated': False} - return {'init': ' '.join(init), 'self': '', 'arguments': arguments, 'all': arguments, 'deprecated': False} - - def extract_constructors(self, data: dict, designated_initializer: bool = None) -> tuple: + def extract_constructors(self, data: dict) -> tuple: """ - - :param data: - :param designated_initializer: - :return: + Preparing tuple with all initiators (constructors) + :param data: list with prepared parameters + :return: tuple with all initiators (constructors) """ mandatory = [] not_mandatory = [] @@ -210,25 +212,21 @@ def extract_constructors(self, data: dict, designated_initializer: bool = None) not_mandatory['all'] = mandatory['arguments'] + not_mandatory['arguments'] not_mandatory['deprecated'] = any([m.deprecated for m in mandatory if hasattr(m, 'deprecated')]) not_mandatory['self'] = re.sub(r'([\w\d]+:)\([\w\d\s<>*]*\)([\w\d]+\s*)', r'\1\2', mandatory['init']) - if not mandatory and designated_initializer: - not_mandatory['init'] = not_mandatory['init'] + ' NS_DESIGNATED_INITIALIZER' result.append(self.constructor_named(**not_mandatory)) if mandatory: - if designated_initializer: - mandatory['init'] = mandatory['init'] + ' NS_DESIGNATED_INITIALIZER' result.insert(0, self.constructor_named(**mandatory)) return tuple(result) - @staticmethod - def evaluate_type(instance, mandatory) -> dict: + def evaluate_type(self, instance) -> dict: """ - - :param instance: - :param mandatory: - :return: + Extracting dictionary with evaluated output types + :param instance: param_type of Param + :return: dictionary with evaluated output types """ + if hasattr(instance, 'name'): + instance.name = self.replace_sync(instance.name) data = OrderedDict() if isinstance(instance, Enum): data['for_name'] = 'enum' @@ -262,8 +260,6 @@ def evaluate_type(instance, mandatory) -> dict: data['type_sdl'] = 'SDLUInt' data['of_class'] = 'NSNumber.class' data['type_sdl'] = 'NSNumber<{}> *'.format(data['type_sdl']) - if not mandatory: - data['type_native'] = data['type_sdl'] elif isinstance(instance, String): data['of_class'] = 'NSString.class' data['type_sdl'] = data['type_native'] = 'NSString *' @@ -275,24 +271,29 @@ def evaluate_type(instance, mandatory) -> dict: def extract_type(self, param: Param) -> dict: """ - - :param param: - :return: + Preparing dictionary with output types information + :param param: sub element of Enum/Function/Struct + :return: dictionary with output types information """ if isinstance(param.param_type, Array): - data = self.evaluate_type(param.param_type.element_type, param.is_mandatory) + data = self.evaluate_type(param.param_type.element_type) data['for_name'] = data['for_name'] + 's' data['type_sdl'] = data['type_native'] = 'NSArray<{}> *'.format(data['type_sdl'].strip()) - return data - return self.evaluate_type(param.param_type, param.is_mandatory) + else: + data = self.evaluate_type(param.param_type) + + if not param.is_mandatory and re.match(r'\w*Int\d*', data['type_native']): + data['type_native'] = data['type_sdl'] + + return data @staticmethod - def param_origin_change(name): + def param_origin_change(name) -> dict: """ - - :param name: - :return: + Based on name preparing common part of output types information + :param name: Param name + :return: dictionary with part of output types information """ return {'origin': name, 'constructor_argument': name, @@ -301,9 +302,9 @@ def param_origin_change(name): def extract_param(self, param: Param): """ - - :param param: - :return: + Preparing self.param_named with prepared params + :param param: Param from initial Model + :return: self.param_named with prepared params """ data = {'constructor_argument_override': None, 'description': self.extract_description(param.description), diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py index dd072544a..f458e243f 100644 --- a/generator/transformers/enums_producer.py +++ b/generator/transformers/enums_producer.py @@ -22,13 +22,15 @@ def __init__(self, enum_class): self.logger = logging.getLogger(self.__class__.__name__) self.param_named = namedtuple('param_named', 'origin description name since value') - def transform(self, item: Enum, render=None) -> dict: + def transform(self, item: Enum, render: dict = None) -> dict: """ - - :param item: - :param render: - :return: + Main entry point for transforming each Enum into output dictionary, + which going to be applied to Jinja2 template + :param item: instance of Enum + :param render: empty dictionary, present in parameter for code consistency + :return: dictionary which going to be applied to Jinja2 template """ + item.name = self.replace_sync(item.name) name = 'SDL{}{}'.format(item.name[:1].upper(), item.name[1:]) tmp = {self.enum_class} imports = {'.h': tmp, '.m': tmp} @@ -44,9 +46,9 @@ def transform(self, item: Enum, render=None) -> dict: def extract_param(self, param: EnumElement): """ - - :param param: - :return: + Preparing self.param_named with prepared params + :param param: EnumElement from initial Model + :return: self.param_named with prepared params """ data = {'origin': param.name, 'description': self.extract_description(param.description, 113), 'since': param.since} @@ -54,7 +56,7 @@ def extract_param(self, param: EnumElement): if re.match(r'^[A-Z]{1,2}\d|\d[A-Z]{1,2}$', param.name): data['name'] = param.name elif re.match(r'(^[a-z\d]+$|^[A-Z\d]+$)', param.name): - data['name'] = param.name.title() # .capitalize() + data['name'] = param.name.title() elif re.match(r'^(?=\w*[a-z])(?=\w*[A-Z])\w+$', param.name): if param.name.endswith('ID'): data['name'] = param.name[:-2] diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py index 5cb562047..01110ac18 100644 --- a/generator/transformers/functions_producer.py +++ b/generator/transformers/functions_producer.py @@ -26,18 +26,18 @@ def __init__(self, paths, names): self.logger = logging.getLogger(self.__class__.__name__) self.common_names = namedtuple('common_names', 'name origin description since') - def transform(self, item: Function, render=None) -> dict: + def transform(self, item: Function, render: dict = None) -> dict: """ - - :param item: - :param render: - :return: + Main entry point for transforming each Enum/Function/Struct into output dictionary, + which going to be applied to Jinja2 template + :param item: instance of Enum/Function/Struct + :param render: dictionary with pre filled entries, which going to be filled/changed by reference + :return: dictionary which going to be applied to Jinja2 template """ list(map(item.params.__delitem__, filter(item.params.__contains__, ['success', 'resultCode', 'info']))) - + item.name = self.replace_sync(item.name) name = 'SDL' + item.name - imports = {'.h': {'enum': set(), 'struct': set()}, - '.m': set()} + imports = {'.h': {'enum': set(), 'struct': set()}, '.m': set()} extends_class = None if item.message_type.name == 'response': extends_class = self.response_class @@ -62,13 +62,13 @@ def transform(self, item: Function, render=None) -> dict: def get_function_names(self, items: dict) -> dict: """ - - :param items: - :return: dict + Standalone method used for preparing SDLRPCFunctionNames collection ready to be applied to Jinja2 template + :param items: collection with all functions from initial Model + :return: collection with transformed element ready to be applied to Jinja2 template """ render = OrderedDict() for item in items.values(): - tmp = {'name': self.title(item.name), + tmp = {'name': self.title(self.replace_sync(item.name)), 'origin': item.name, 'description': self.extract_description(item.description), 'since': item.since} @@ -76,31 +76,38 @@ def get_function_names(self, items: dict) -> dict: return {'params': sorted(render.values(), key=lambda a: a.name)} - def get_simple_params(self, functions: dict, structs: dict) -> dict: + def evaluate(self, element) -> dict: """ - :param functions: - :param structs: - :return: + Internal evaluator used for preparing SDLRPCParameterNames collection + :param element: Param from initial Model + :return: dictionary with evaluated part of output collection """ + origin = element.name + name = self.replace_sync(element.name) + # if isinstance(element.param_type, (Integer, Float, Boolean, String)): + return {name: self.common_names(**{ + 'name': self.title(name), + 'origin': origin, + 'description': self.extract_description(element.description), + 'since': element.since})} + # return OrderedDict() - def evaluate(element): - # if isinstance(element.param_type, (Integer, Float, Boolean, String)): - return {element.name: self.common_names(**{ - 'name': self.title(element.name), - 'origin': element.name, - 'description': self.extract_description(element.description), - 'since': element.since})} - # return OrderedDict() - + def get_simple_params(self, functions: dict, structs: dict) -> dict: + """ + Standalone method used for preparing SDLRPCParameterNames collection ready to be applied to Jinja2 template + :param functions: collection with all functions from initial Model + :param structs: collection with all structs from initial Model + :return: collection with transformed element ready to be applied to Jinja2 template + """ render = OrderedDict() for func in functions.values(): for param in func.params.values(): - render.update(evaluate(param)) + render.update(self.evaluate(param)) for struct in structs.values(): - render.update(evaluate(struct)) + render.update(self.evaluate(struct)) for param in struct.members.values(): - render.update(evaluate(param)) + render.update(self.evaluate(param)) return {'params': sorted(render.values(), key=lambda a: a.name)} diff --git a/generator/transformers/generate_error.py b/generator/transformers/generate_error.py deleted file mode 100644 index 3fe1a75ed..000000000 --- a/generator/transformers/generate_error.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -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 index 86a596095..beb652a3a 100644 --- a/generator/transformers/structs_producer.py +++ b/generator/transformers/structs_producer.py @@ -21,13 +21,15 @@ def __init__(self, struct_class, enum_names): self.struct_class = struct_class self.logger = logging.getLogger(self.__class__.__name__) - def transform(self, item: Struct, render=None) -> dict: + def transform(self, item: Struct, render: dict = None) -> dict: """ - - :param item: - :param render: - :return: + Main entry point for transforming each Enum/Function/Struct into output dictionary, + which going to be applied to Jinja2 template + :param item: instance of Enum/Function/Struct + :param render: dictionary with pre filled entries, which going to be filled/changed by reference + :return: dictionary which going to be applied to Jinja2 template """ + item.name = self.replace_sync(item.name) name = 'SDL' + item.name imports = {'.h': {'enum': set(), 'struct': set()}, '.m': set()} imports['.h']['enum'].add(self.struct_class) From 9a0d9f6c8b0d2c553125880410dcf4db01f85115 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Tue, 24 Mar 2020 14:26:32 +0100 Subject: [PATCH 20/28] Apply suggestions from code review --- generator/templates/enums/template.h | 2 +- generator/templates/enums/template_numeric.h | 13 -------- generator/test/runner.py | 15 ++++++++- generator/test/test_enums.py | 26 +++++++++++---- generator/test/test_functions.py | 34 +++++++++++++++++++- generator/test/test_structs.py | 14 +++++++- generator/transformers/enums_producer.py | 6 +--- 7 files changed, 81 insertions(+), 29 deletions(-) delete mode 100644 generator/templates/enums/template_numeric.h diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index 033626c1c..122fe0f15 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -1,4 +1,4 @@ -{#- String based enum. If the enum element doesn't have values it going to be string enum -#} +{#- String based enum -#} {% include 'copyright.txt' %} {% block imports -%} {%- for import in imports %} diff --git a/generator/templates/enums/template_numeric.h b/generator/templates/enums/template_numeric.h deleted file mode 100644 index f16e8f834..000000000 --- a/generator/templates/enums/template_numeric.h +++ /dev/null @@ -1,13 +0,0 @@ -{#- Numeric based enum. If the enum element values are numeric the enum should be a numerical. - This template extends base "templates/enums/template.h" and overriding block body -#} -{% extends "template.h" %} -{%- block body %} -{% include 'description.jinja' %} -typedef NS_ENUM(NSUInteger, {{ name }}){ -{% for param in params %} -{%- macro description_param() -%}{% include 'description_param.jinja' %}{%- endmacro -%} -{{ description_param()|indent(4, True) }} - {{ name }}{{ param.name }} = {{ param.value }}{{ " __deprecated" if param.deprecated }}{{',' if not loop.last }} -{% endfor -%} -}; -{% endblock -%} \ No newline at end of file diff --git a/generator/test/runner.py b/generator/test/runner.py index ac72c6a2e..2cb413227 100644 --- a/generator/test/runner.py +++ b/generator/test/runner.py @@ -36,7 +36,17 @@ def config_logging(): def main(): """ - Entry point for parser and generator + Without performing Tests (simple instances initialization) there are following initial test code coverage: + generator/transformers/common_producer.py 21% + generator/transformers/enums_producer.py 24% + generator/transformers/functions_producer.py 18% + generator/transformers/structs_producer.py 32% + + After performing Tests there are following initial test code coverage: + generator/transformers/common_producer.py 100% + generator/transformers/enums_producer.py 100% + generator/transformers/functions_producer.py 100% + generator/transformers/structs_producer.py 100% """ config_logging() suite = TestSuite() @@ -51,4 +61,7 @@ def main(): if __name__ == '__main__': + """ + Entry point for parser and generator. + """ main() diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py index b7205e984..912dafe30 100644 --- a/generator/test/test_enums.py +++ b/generator/test/test_enums.py @@ -12,6 +12,10 @@ class TestEnumsProducer(TestCase): class EnumsProducer. All names of Enums and nested elements doesn't reflating with real Enums and could be replaces with some meaningless names. + + After performing Tests there are following initial test code coverage: + generator/transformers/common_producer.py 34% + generator/transformers/enums_producer.py 100% """ def setUp(self): @@ -20,6 +24,10 @@ def setUp(self): self.producer = EnumsProducer('SDLEnum') def test_FunctionID(self): + """ + generator/transformers/common_producer.py 34% + generator/transformers/enums_producer.py 80% + """ elements = OrderedDict() elements['RESERVED'] = EnumElement(name='RESERVED', value=0) elements['RegisterAppInterfaceID'] = EnumElement(name='RegisterAppInterfaceID', hex_value=1) @@ -31,16 +39,20 @@ def test_FunctionID(self): expected['name'] = 'SDLFunctionID' expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} expected['params'] = ( - self.producer.param_named(description=[], name='Reserved', origin='RESERVED', since=None, value=0), + self.producer.param_named(description=[], name='Reserved', origin='RESERVED', since=None), self.producer.param_named(description=[], name='RegisterAppInterface', origin='RegisterAppInterfaceID', - since=None, value=None), + since=None), self.producer.param_named(description=[], name='PerformAudioPassThru', origin='PerformAudioPassThruID', - since=None, value=None),) + since=None),) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) def test_TextFieldName(self): + """ + generator/transformers/common_producer.py 34% + generator/transformers/enums_producer.py 98% + """ elements = OrderedDict() elements['SUCCESS'] = EnumElement(name='SUCCESS') elements['mainField1'] = EnumElement(name='mainField1') @@ -53,11 +65,11 @@ def test_TextFieldName(self): expected['name'] = 'SDLTextFieldName' expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} expected['params'] = ( - self.producer.param_named(description=[], name='Success', origin='SUCCESS', since=None, value=None), - self.producer.param_named(description=[], name='MainField1', origin='mainField1', since=None, value=None), - self.producer.param_named(description=[], name='H264', origin='H264', since=None, value=None), + self.producer.param_named(description=[], name='Success', origin='SUCCESS', since=None), + self.producer.param_named(description=[], name='MainField1', origin='mainField1', since=None), + self.producer.param_named(description=[], name='H264', origin='H264', since=None), self.producer.param_named(description=[], name='UnsupportedRequest', origin='UNSUPPORTED_REQUEST', - since=None, value=None)) + since=None)) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) diff --git a/generator/test/test_functions.py b/generator/test/test_functions.py index a481317c3..44af42c7e 100644 --- a/generator/test/test_functions.py +++ b/generator/test/test_functions.py @@ -19,8 +19,12 @@ class TestFunctionsProducer(TestCase): """ The structures of tests in this class was prepared to cover all possible combinations of code branching in tested class FunctionsProducer. - All names of Functions and nested elements doesn't reflating with real Enums + All names of Functions and nested elements doesn't reflating with real Functions and could be replaces with some meaningless names. + + After performing Tests there are following initial test code coverage: + generator/transformers/common_producer.py 99% + generator/transformers/functions_producer.py 100% """ def setUp(self): @@ -37,6 +41,10 @@ def setUp(self): self.producer = FunctionsProducer(paths, names) def test_process_function_name(self): + """ + generator/transformers/common_producer.py 29% + generator/transformers/functions_producer.py 61% + """ functions = { 'RegisterAppInterface': Function(name='RegisterAppInterface', function_id=EnumElement(name='RegisterAppInterfaceID'), since='3.0.0', @@ -84,6 +92,10 @@ def test_process_function_name(self): self.assertCountEqual(expected, actual['params']) def test_RegisterAppInterfaceRequest(self): + """ + generator/transformers/common_producer.py 85% + generator/transformers/functions_producer.py 63% + """ params = OrderedDict() params['syncMsgVersion'] = Param( name='syncMsgVersion', param_type=Struct(name='SyncMsgVersion', description=['Specifies the'], members={ @@ -177,6 +189,10 @@ def test_RegisterAppInterfaceRequest(self): self.assertDictEqual(expected, actual) def test_RegisterAppInterfaceResponse(self): + """ + generator/transformers/common_producer.py 82% + generator/transformers/functions_producer.py 63% + """ params = OrderedDict() params['success'] = Param(name='success', param_type=Boolean(), description=[' True if '], is_mandatory=False) params['language'] = Param(name='language', is_mandatory=False, param_type=Enum(name='Language', elements={ @@ -238,6 +254,10 @@ def test_RegisterAppInterfaceResponse(self): self.assertDictEqual(expected, actual) def test_OnHMIStatus(self): + """ + generator/transformers/common_producer.py 66% + generator/transformers/functions_producer.py 65% + """ item = Function(name='OnHMIStatus', function_id=EnumElement(name='OnHMIStatusID'), message_type=EnumElement(name='notification'), params={ 'hmiLevel': Param(name='hmiLevel', param_type=Enum(name='HMILevel')) @@ -267,6 +287,10 @@ def test_OnHMIStatus(self): self.assertDictEqual(expected, actual) def test_CreateWindow(self): + """ + generator/transformers/common_producer.py 82% + generator/transformers/functions_producer.py 63% + """ params = OrderedDict() params['windowID'] = Param(name='windowID', param_type=Integer()) params['cmdID'] = Param(name='cmdID', param_type=Integer(max_value=2000000000, min_value=0)) @@ -350,6 +374,10 @@ def test_CreateWindow(self): self.assertDictEqual(expected, actual) def test_CreateInteractionChoiceSet(self): + """ + generator/transformers/common_producer.py 67% + generator/transformers/functions_producer.py 63% + """ params = OrderedDict() params['choiceSet'] = Param(name='choiceSet', param_type=Array(element_type=Struct(name='Choice'))) item = Function(name='CreateInteractionChoiceSet', function_id=EnumElement(name='CreateInteractionChoiceSetID'), @@ -379,6 +407,10 @@ def test_CreateInteractionChoiceSet(self): self.assertDictEqual(expected, actual) def test_SetDisplayLayout(self): + """ + generator/transformers/common_producer.py 66% + generator/transformers/functions_producer.py 63% + """ params = OrderedDict() params['displayLayout'] = Param(name='displayLayout', param_type=String(max_length=500, min_length=1)) item = Function(name='SetDisplayLayout', function_id=EnumElement(name='SetDisplayLayoutID'), diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py index 77186799e..838e13254 100644 --- a/generator/test/test_structs.py +++ b/generator/test/test_structs.py @@ -12,8 +12,12 @@ class TestStructsProducer(TestCase): """ The structures of tests in this class was prepared to cover all possible combinations of code branching in tested class StructsProducer. - All names of Structs and nested elements doesn't reflating with real Enums + All names of Structs and nested elements doesn't reflating with real Structs and could be replaces with some meaningless names. + + After performing Tests there are following initial test code coverage: + generator/transformers/common_producer.py 72% + generator/transformers/structs_producer.py 100% """ def setUp(self): @@ -22,6 +26,10 @@ def setUp(self): self.producer = StructsProducer('SDLRPCStruct', ['Image']) def test_CloudAppProperties(self): + """ + generator/transformers/common_producer.py 64% + generator/transformers/structs_producer.py 100% + """ item = Struct(name='CloudAppProperties', members={ 'appID': Param(name='appID', param_type=String()) }) @@ -49,6 +57,10 @@ def test_CloudAppProperties(self): self.assertDictEqual(expected, actual) def test_TouchEvent(self): + """ + generator/transformers/common_producer.py 69% + generator/transformers/structs_producer.py 100% + """ item = Struct(name='TouchEvent', members={ 'id': Param(name='id', param_type=Integer(max_value=9, min_value=0)) }) diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py index f458e243f..e9f1c71dd 100644 --- a/generator/transformers/enums_producer.py +++ b/generator/transformers/enums_producer.py @@ -20,7 +20,7 @@ def __init__(self, enum_class): container_name='elements') self.enum_class = enum_class self.logger = logging.getLogger(self.__class__.__name__) - self.param_named = namedtuple('param_named', 'origin description name since value') + self.param_named = namedtuple('param_named', 'origin description name since') def transform(self, item: Enum, render: dict = None) -> dict: """ @@ -40,8 +40,6 @@ def transform(self, item: Enum, render: dict = None) -> dict: render['name'] = name render['imports'] = imports super(EnumsProducer, self).transform(item, render) - if any(map(lambda p: p.value, render['params'])): - render['template'] = 'enums/template_numeric' return render def extract_param(self, param: EnumElement): @@ -69,6 +67,4 @@ def extract_param(self, param: EnumElement): name.append(item.title()) data['name'] = ''.join(name) - data['value'] = param.value - return self.param_named(**data) From ede8a54d8f35cbb6a7985a1e04a87a2b842fe5cb Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Wed, 25 Mar 2020 18:00:54 +0100 Subject: [PATCH 21/28] Apply suggestions from code review --- generator/generator.py | 17 ----------------- generator/templates/SDLRPCFunctionNames.h | 3 ++- generator/transformers/functions_producer.py | 4 ++-- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/generator/generator.py b/generator/generator.py index fbef269e7..058484645 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -2,7 +2,6 @@ Generator """ import asyncio -import json import logging import re import sys @@ -10,7 +9,6 @@ from collections import namedtuple, OrderedDict from datetime import datetime, date from inspect import getfile -from json import JSONDecodeError from os.path import basename, join from pathlib import Path from re import findall @@ -32,7 +30,6 @@ from transformers.structs_producer import StructsProducer except ImportError as error: print('%s.\nprobably you did not initialize submodule', error) - ParseError = Parser = Interface = Common = EnumsProducer = FunctionsProducer = StructsProducer = None sys.exit(1) @@ -280,20 +277,6 @@ def get_paths(self, file_name: Path = ROOT.joinpath('paths.ini')): return self.paths_named(**data) - async def get_mappings(self, file: Path = ROOT.joinpath('mapping.json')): - """ - The key name in *.json is equal to property named in jinja2 templates - :param file: path to file with manual mappings - :return: dictionary with custom manual mappings - """ - try: - with file.open('r') as handler: - content = handler.readlines() - return json.loads(''.join(content)) - except (FileNotFoundError, JSONDecodeError) as error1: - self.logger.error('Failure to get mappings %s', error1) - return OrderedDict() - def write_file(self, file: Path, templates: list, data: dict): """ Calling producer/transformer instance to transform initial Model to dict used in jinja2 templates. diff --git a/generator/templates/SDLRPCFunctionNames.h b/generator/templates/SDLRPCFunctionNames.h index 39a6e27d8..3126c8169 100644 --- a/generator/templates/SDLRPCFunctionNames.h +++ b/generator/templates/SDLRPCFunctionNames.h @@ -8,7 +8,8 @@ */ typedef SDLEnum SDLRPCFunctionName SDL_SWIFT_ENUM; {% for param in params %} -# Create inline documentation for the parameter if there's a `since` variable and / or a description +{#- description if exist in source xml, will be putted here + since if exist in source xml, will be putted here -#} {%- if param.description or param.since %} /** {%- if param.description %} diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py index 01110ac18..bad4d0f0f 100644 --- a/generator/transformers/functions_producer.py +++ b/generator/transformers/functions_producer.py @@ -109,5 +109,5 @@ def get_simple_params(self, functions: dict, structs: dict) -> dict: render.update(self.evaluate(struct)) for param in struct.members.values(): render.update(self.evaluate(param)) - - return {'params': sorted(render.values(), key=lambda a: a.name)} + unique = dict(zip(list(map(lambda l: l.name, render.values())), render.values())) + return {'params': sorted(unique.values(), key=lambda a: a.name)} From cdc374e39c4eec6e529db16fec28c3d21c4be60e Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Fri, 27 Mar 2020 01:32:46 +0100 Subject: [PATCH 22/28] reserved keywords --- generator/generator.py | 77 ++++++++++++++------ generator/rpc_spec | 2 +- generator/test/test_enums.py | 10 ++- generator/test/test_functions.py | 8 +- generator/test/test_structs.py | 22 ++++-- generator/transformers/common_producer.py | 41 ++++++++--- generator/transformers/enums_producer.py | 10 ++- generator/transformers/functions_producer.py | 13 ++-- generator/transformers/structs_producer.py | 11 ++- 9 files changed, 138 insertions(+), 56 deletions(-) diff --git a/generator/generator.py b/generator/generator.py index 058484645..8d2a8d63d 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -244,35 +244,66 @@ def versions_compatibility_validating(self): self.logger.info('Parser type: %s, version %s,\tGenerator version %s', basename(getfile(Parser().__class__)), parser_origin, generator_origin) + def get_file_content(self, file_name: Path) -> list: + """ + + :param file_name: + :return: + """ + try: + with file_name.open('r') as file: + content = file.readlines() + return content + except FileNotFoundError as message1: + self.logger.error(message1) + return [] + + def get_key_words(self, file_name=ROOT.joinpath('rpc_spec/RpcParser/RESERVED_KEYWORDS')): + """ + :param file_name: + :return: + """ + content = self.get_file_content(file_name) + content = tuple(map(lambda e: re.sub(r'\n', r'', e).strip().casefold(), content)) + try: + start_index = content.index('# ios library') + content = content[start_index + 1:len(content)] + content = tuple(filter(lambda e: not re.search(r'^#+\s+.+|^$', e), content)) + self.logger.debug('key_words: %s', ', '.join(content)) + return content + except (IndexError, ValueError, StopIteration) as error1: + self.logger.error('Error while getting key_words, %s %s', type(error1).__name__, error1) + return [] + def get_paths(self, file_name: Path = ROOT.joinpath('paths.ini')): """ Getting and validating parent classes names :param file_name: path to file with container :return: namedtuple with container to key elements """ - data = OrderedDict() - try: - with file_name.open('r') as file: - for line in file: - if line.startswith('#'): - self.logger.warning('commented property %s, which will be skipped', line.strip()) - continue - if re.match(r'^(\w+)\s?=\s?(.+)', line): - if len(line.split('=')) > 2: - self.logger.critical('can not evaluate value, too many separators %s', str(line)) - sys.exit(1) - name, var = line.partition('=')[::2] - if name.strip() in data: - self.logger.critical('duplicate key %s', name) - sys.exit(1) - data[name.strip().lower()] = var.strip() - except FileNotFoundError as message1: - self.logger.critical(message1) + content = self.get_file_content(file_name) + if not content: + self.logger.critical('%s not found', file_name) sys.exit(1) + data = OrderedDict() + + for line in content: + if line.startswith('#'): + self.logger.warning('commented property %s, which will be skipped', line.strip()) + continue + if re.match(r'^(\w+)\s?=\s?(.+)', line): + if len(line.split('=')) > 2: + self.logger.critical('can not evaluate value, too many separators %s', str(line)) + sys.exit(1) + name, var = line.partition('=')[::2] + if name.strip() in data: + self.logger.critical('duplicate key %s', name) + sys.exit(1) + data[name.strip().lower()] = var.strip() missed = list(set(self.paths_named._fields) - set(data.keys())) if missed: - self.logger.critical('in %s missed fields: %s ', file, str(missed)) + self.logger.critical('in %s missed fields: %s ', content, str(missed)) sys.exit(1) return self.paths_named(**data) @@ -439,13 +470,15 @@ def main(self): filtered, names = self.filter_pattern(interface, args.regex_pattern) tasks = [] - functions_transformer = FunctionsProducer(paths, names) + key_words = self.get_key_words() + + functions_transformer = FunctionsProducer(paths, names, key_words) if args.enums and filtered.enums: tasks.append(self.process_main(args.skip, args.overwrite, filtered.enums, - EnumsProducer(paths.enum_class))) + EnumsProducer(paths.enum_class, key_words))) if args.structs and filtered.structs: tasks.append(self.process_main(args.skip, args.overwrite, filtered.structs, - StructsProducer(paths.struct_class, names))) + StructsProducer(paths.struct_class, names, key_words))) if args.functions and filtered.functions: tasks.append(self.process_main(args.skip, args.overwrite, filtered.functions, functions_transformer)) tasks.append(self.process_function_name(args.skip, args.overwrite, interface.functions, diff --git a/generator/rpc_spec b/generator/rpc_spec index bf1466206..c5132e001 160000 --- a/generator/rpc_spec +++ b/generator/rpc_spec @@ -1 +1 @@ -Subproject commit bf14662066e3d9c2e7b32b56f1fa8e9dad8dceb1 +Subproject commit c5132e0016cf34b30fec2a2687c59cbfd44c543a diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py index 912dafe30..b3a5a7f42 100644 --- a/generator/test/test_enums.py +++ b/generator/test/test_enums.py @@ -1,6 +1,11 @@ from collections import OrderedDict from unittest import TestCase +try: + from generator import Generator +except ImportError as error: + from generator.generator import Generator + from model.enum import Enum from model.enum_element import EnumElement from transformers.enums_producer import EnumsProducer @@ -20,8 +25,9 @@ class EnumsProducer. def setUp(self): self.maxDiff = None + key_words = Generator().get_key_words() - self.producer = EnumsProducer('SDLEnum') + self.producer = EnumsProducer('SDLEnum', key_words) def test_FunctionID(self): """ @@ -65,7 +71,7 @@ def test_TextFieldName(self): expected['name'] = 'SDLTextFieldName' expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} expected['params'] = ( - self.producer.param_named(description=[], name='Success', origin='SUCCESS', since=None), + self.producer.param_named(description=[], name='SuccessParam', origin='SUCCESS_PARAM', since=None), self.producer.param_named(description=[], name='MainField1', origin='mainField1', since=None), self.producer.param_named(description=[], name='H264', origin='H264', since=None), self.producer.param_named(description=[], name='UnsupportedRequest', origin='UNSUPPORTED_REQUEST', diff --git a/generator/test/test_functions.py b/generator/test/test_functions.py index 44af42c7e..af858d5b8 100644 --- a/generator/test/test_functions.py +++ b/generator/test/test_functions.py @@ -2,6 +2,11 @@ from collections import namedtuple, OrderedDict from unittest import TestCase +try: + from generator import Generator +except ImportError as error: + from generator.generator import Generator + from model.array import Array from model.boolean import Boolean from model.enum import Enum @@ -29,6 +34,7 @@ class FunctionsProducer. def setUp(self): self.maxDiff = None + key_words = Generator().get_key_words() Paths = namedtuple('Paths', 'request_class response_class notification_class function_names parameter_names') paths = Paths(request_class='SDLRPCRequest', @@ -38,7 +44,7 @@ def setUp(self): parameter_names='SDLRPCParameterNames') names = ('FileType', 'Language', 'SyncMsgVersion', 'TemplateColorScheme', 'TTSChunk', 'Choice') - self.producer = FunctionsProducer(paths, names) + self.producer = FunctionsProducer(paths, names, key_words) def test_process_function_name(self): """ diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py index 838e13254..9ddd8fef9 100644 --- a/generator/test/test_structs.py +++ b/generator/test/test_structs.py @@ -1,6 +1,11 @@ from collections import OrderedDict from unittest import TestCase +try: + from generator import Generator +except ImportError as error: + from generator.generator import Generator + from model.integer import Integer from model.param import Param from model.string import String @@ -22,8 +27,9 @@ class StructsProducer. def setUp(self): self.maxDiff = None + key_words = Generator().get_key_words() - self.producer = StructsProducer('SDLRPCStruct', ['Image']) + self.producer = StructsProducer('SDLRPCStruct', ['Image'], key_words) def test_CloudAppProperties(self): """ @@ -71,19 +77,19 @@ def test_TouchEvent(self): expected['imports'] = {'.h': {'enum': {'SDLRPCStruct'}, 'struct': set()}, '.m': set()} expected['params'] = ( self.producer.param_named( - constructor_argument='touchEventId', constructor_argument_override=None, - constructor_prefix='TouchEventId', deprecated=False, + constructor_argument='idParam', constructor_argument_override=None, + constructor_prefix='IdParam', deprecated=False, description=['{"default_value": null, "max_value": 9, "min_value": 0}'], for_name='object', - mandatory=True, method_suffix='TouchEventId', modifier='strong', of_class='NSNumber.class', - origin='touchEventId', since=None, type_native='UInt8', type_sdl='NSNumber *'),) + mandatory=True, method_suffix='IdParam', modifier='strong', of_class='NSNumber.class', + origin='idParam', since=None, type_native='UInt8', type_sdl='NSNumber *'),) argument = [ - self.producer.argument_named(variable='touchEventId', deprecated=False, - constructor_argument='@(touchEventId)', origin='touchEventId')] + self.producer.argument_named(variable='idParam', deprecated=False, + constructor_argument='@(idParam)', origin='idParam')] expected['constructors'] = (self.producer.constructor_named( all=argument, arguments=argument, deprecated=False, self='', - init='TouchEventId:(UInt8)touchEventId'),) + init='IdParam:(UInt8)idParam'),) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index 3b2adab76..4719a48e6 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -5,7 +5,7 @@ import logging import re import textwrap -from abc import ABC +from abc import ABC, abstractmethod from collections import OrderedDict, namedtuple from model.array import Array @@ -24,10 +24,10 @@ class InterfaceProducerCommon(ABC): All Enums/Structs/Functions Producer are inherited from this class and using features of it """ - def __init__(self, container_name, names=()): + def __init__(self, names=(), key_words=()): self.logger = logging.getLogger(self.__class__.__name__) - self.container_name = container_name self.names = list(map(lambda e: self.replace_sync(e), names)) + self.key_words = key_words self.param_named = namedtuple('param_named', 'origin constructor_argument constructor_prefix deprecated mandatory since ' 'method_suffix of_class type_native type_sdl modifier for_name description ' @@ -35,6 +35,11 @@ def __init__(self, container_name, names=()): self.constructor_named = namedtuple('constructor', 'init self arguments all deprecated') self.argument_named = namedtuple('argument', 'origin constructor_argument variable deprecated') + @property + @abstractmethod + def container_name(self): + pass + def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: """ Main entry point for transforming each Enum/Function/Struct into output dictionary, @@ -55,9 +60,9 @@ def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: render['params'] = OrderedDict() for param in getattr(item, self.container_name).values(): - param.name = self.replace_sync(param.name) - if param.name.lower() == 'id': - param.name = self.minimize_first(item.name) + self.title(param.name) + # if param.name.lower() == 'id': + # param.name = self.minimize_first(item.name) + self.title(param.name) + param.name = self.replace_keywords(param.name) render['params'][param.name] = self.extract_param(param) if isinstance(item, (Struct, Function)): self.extract_imports(param, render['imports']) @@ -68,13 +73,29 @@ def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: render['params'] = tuple(render['params'].values()) return render + def replace_keywords(self, name: str = '') -> str: + """ + if :param name in self.key_words, :return: name += 'Param' + :param name: string with item name + """ + if name.casefold() in 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) + @staticmethod - def replace_sync(name: str = '') -> str: + def replace_sync(name): """ :param name: string with item name - :return: string with replaced 'sync' to 'sdl' + :return: string with replaced 'sync' to 'Sdl' """ - return re.sub(r'^([sS])ync(.+)$', r'\1dl\2', name) + if name: + name = re.sub(r'^([sS])ync(.+)$', r'\1dl\2', name) + return name def extract_imports(self, param: Param, imports: dict): """ @@ -226,7 +247,7 @@ def evaluate_type(self, instance) -> dict: :return: dictionary with evaluated output types """ if hasattr(instance, 'name'): - instance.name = self.replace_sync(instance.name) + instance.name = self.replace_keywords(instance.name) data = OrderedDict() if isinstance(instance, Enum): data['for_name'] = 'enum' diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py index e9f1c71dd..cd44a640d 100644 --- a/generator/transformers/enums_producer.py +++ b/generator/transformers/enums_producer.py @@ -15,13 +15,17 @@ class EnumsProducer(InterfaceProducerCommon): Enums transformer """ - def __init__(self, enum_class): - super(EnumsProducer, self).__init__( - container_name='elements') + def __init__(self, enum_class, key_words): + super(EnumsProducer, self).__init__(key_words=key_words) + self._container_name = 'elements' self.enum_class = enum_class self.logger = logging.getLogger(self.__class__.__name__) self.param_named = namedtuple('param_named', 'origin description name since') + @property + def container_name(self): + return self._container_name + def transform(self, item: Enum, render: dict = None) -> dict: """ Main entry point for transforming each Enum into output dictionary, diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py index bad4d0f0f..8eaf5fbc4 100644 --- a/generator/transformers/functions_producer.py +++ b/generator/transformers/functions_producer.py @@ -14,10 +14,9 @@ class FunctionsProducer(InterfaceProducerCommon): Functions transformer """ - def __init__(self, paths, names): - super(FunctionsProducer, self).__init__( - container_name='params', - names=names) + def __init__(self, paths, names, key_words): + super(FunctionsProducer, self).__init__(names=names, key_words=key_words) + self._container_name = 'params' self.request_class = paths.request_class self.response_class = paths.response_class self.notification_class = paths.notification_class @@ -26,6 +25,10 @@ def __init__(self, paths, names): self.logger = logging.getLogger(self.__class__.__name__) self.common_names = namedtuple('common_names', 'name origin description since') + @property + def container_name(self): + return self._container_name + def transform(self, item: Function, render: dict = None) -> dict: """ Main entry point for transforming each Enum/Function/Struct into output dictionary, @@ -68,7 +71,7 @@ def get_function_names(self, items: dict) -> dict: """ render = OrderedDict() for item in items.values(): - tmp = {'name': self.title(self.replace_sync(item.name)), + tmp = {'name': self.title(self.replace_keywords(item.name)), 'origin': item.name, 'description': self.extract_description(item.description), 'since': item.since} diff --git a/generator/transformers/structs_producer.py b/generator/transformers/structs_producer.py index beb652a3a..154d34f81 100644 --- a/generator/transformers/structs_producer.py +++ b/generator/transformers/structs_producer.py @@ -14,13 +14,16 @@ class StructsProducer(InterfaceProducerCommon): Structs transformer """ - def __init__(self, struct_class, enum_names): - super(StructsProducer, self).__init__( - container_name='members', - names=enum_names) + def __init__(self, struct_class, enum_names, key_words): + super(StructsProducer, self).__init__(names=enum_names, key_words=key_words) + self._container_name = 'members' self.struct_class = struct_class self.logger = logging.getLogger(self.__class__.__name__) + @property + def container_name(self): + return self._container_name + def transform(self, item: Struct, render: dict = None) -> dict: """ Main entry point for transforming each Enum/Function/Struct into output dictionary, From 97fc74359dc0ac2b615be9fb615416edb22becd9 Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Mon, 30 Mar 2020 21:09:17 +0200 Subject: [PATCH 23/28] echanced deprecated view --- generator/generator.py | 13 +++--- generator/templates/base_struct_function.h | 13 ++++-- generator/templates/base_struct_function.m | 43 +++++++++++--------- generator/templates/enums/template.h | 9 +++- generator/templates/functions/template.m | 5 ++- generator/test/test_functions.py | 6 ++- generator/test/test_structs.py | 2 +- generator/transformers/common_producer.py | 19 +++++---- generator/transformers/functions_producer.py | 4 +- generator/transformers/structs_producer.py | 4 +- 10 files changed, 72 insertions(+), 46 deletions(-) diff --git a/generator/generator.py b/generator/generator.py index 8d2a8d63d..6192b6816 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -423,7 +423,8 @@ def filter_pattern(interface, pattern): :param pattern: regex pattern (string) :return: Model with items which match with regex pattern """ - names = tuple(interface.enums.keys()) + tuple(interface.structs.keys()) + enum_names = tuple(interface.enums.keys()) + struct_names = tuple(interface.structs.keys()) if pattern: match = {key: OrderedDict() for key in vars(interface).keys()} @@ -432,8 +433,8 @@ def filter_pattern(interface, pattern): if key == 'params': continue match[key].update({name: item for name, item in value.items() if re.match(pattern, item.name)}) - return Interface(**match), names - return interface, names + return Interface(**match), enum_names, struct_names + return interface, enum_names, struct_names async def parser(self, source_xml, source_xsd): """ @@ -467,18 +468,18 @@ def main(self): self.env = [args.templates_directory] + [join(args.templates_directory, k) for k in vars(interface).keys()] - filtered, names = self.filter_pattern(interface, args.regex_pattern) + filtered, enum_names, struct_names = self.filter_pattern(interface, args.regex_pattern) tasks = [] key_words = self.get_key_words() - functions_transformer = FunctionsProducer(paths, names, key_words) + functions_transformer = FunctionsProducer(paths, enum_names, struct_names, key_words) if args.enums and filtered.enums: tasks.append(self.process_main(args.skip, args.overwrite, filtered.enums, EnumsProducer(paths.enum_class, key_words))) if args.structs and filtered.structs: tasks.append(self.process_main(args.skip, args.overwrite, filtered.structs, - StructsProducer(paths.struct_class, names, key_words))) + StructsProducer(paths.struct_class, enum_names, struct_names, key_words))) if args.functions and filtered.functions: tasks.append(self.process_main(args.skip, args.overwrite, filtered.functions, functions_transformer)) tasks.append(self.process_function_name(args.skip, args.overwrite, interface.functions, diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h index 57fb1a6de..a0c697f64 100644 --- a/generator/templates/base_struct_function.h +++ b/generator/templates/base_struct_function.h @@ -29,12 +29,12 @@ NS_ASSUME_NONNULL_BEGIN {%- endfor %} * @return A {{name}} object */ -{%- if c.deprecated %} +{%- if deprecated or c.deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} - (instancetype)initWith{{c.init}}; -{%- if c.deprecated %} +{%- if deprecated or c.deprecated %} #pragma clang diagnostic pop {%- endif %} {% endfor -%} @@ -42,8 +42,15 @@ NS_ASSUME_NONNULL_BEGIN {%- block methods %} {%- for param in params %} {%- include 'description_param.jinja' %} +{%- if deprecated or param.deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} @property ({{'nullable, ' if not param.mandatory}}{{param.modifier}}, nonatomic) {{param.type_sdl}}{{param.origin}}{{' __deprecated' if param.deprecated and param.deprecated is sameas true }}; -{% endfor -%} +{%- if deprecated or param.deprecated %} +#pragma clang diagnostic pop +{%- endif %} +{% endfor %} {%- endblock %} @end diff --git a/generator/templates/base_struct_function.m b/generator/templates/base_struct_function.m index 610a31d90..4c991b1ad 100644 --- a/generator/templates/base_struct_function.m +++ b/generator/templates/base_struct_function.m @@ -10,31 +10,30 @@ {%- endblock %} NS_ASSUME_NONNULL_BEGIN - +{% if deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} @implementation {{name}} +{%- if deprecated %} +#pragma clang diagnostic pop +{%- endif %} {% block constructors %} {%- for c in constructors %} +{%- if deprecated or c.deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} - (instancetype)initWith{{c.init}} { -{%- if c.deprecated %} - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- if deprecated or c.deprecated %} +#pragma clang diagnostic pop {%- endif %} self = [{{ 'self' if c.self else 'super' }} init{{ 'With' + c.self if c.self and c.self is string }}]; -{%- if c.deprecated %} - #pragma clang diagnostic pop -{%- endif %} if (!self) { return nil; } {%- for a in c.arguments %} - {%- if a.deprecated %} - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - {%- endif %} self.{{a.origin}} = {{a.constructor_argument}}; - {%- if a.deprecated %} - #pragma clang diagnostic pop - {%- endif %} {%- endfor %} return self; } @@ -42,23 +41,29 @@ - (instancetype)initWith{{c.init}} { {% endblock -%} {%- block methods %} {%- for p in params %} -{%- if p.deprecated %} +{%- if deprecated or p.deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} - (void)set{{p.origin|title}}:({{'nullable ' if not p.mandatory}}{{p.type_generic}}{{p.type_sdl|trim}}){{p.origin}} { +{%- if deprecated or p.deprecated %} +#pragma clang diagnostic pop +{%- endif %} [self.{{parameters_store}} sdl_setObject:{{p.origin}} forName:SDLRPCParameterName{{p.method_suffix}}]; } - +{% if deprecated or p.deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} - ({{'nullable ' if not p.mandatory}}{{p.type_generic}}{{p.type_sdl|trim}}){{p.origin}} { +{%- if deprecated or p.deprecated %} +#pragma clang diagnostic pop +{%- endif %} {% if p.mandatory -%} NSError *error = nil; {% endif -%} return [self.{{parameters_store}} sdl_{{p.for_name}}ForName:SDLRPCParameterName{{p.method_suffix}}{{' ofClass:'+p.of_class if p.of_class}} error:{{'&error' if p.mandatory else 'nil'}}]; } -{%- if p.deprecated %} -#pragma clang diagnostic pop -{%- endif %} {% endfor %} {%- endblock %} @end diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index 122fe0f15..a0dea105a 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -10,6 +10,13 @@ typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; {% for param in params %} {%- include 'description_param.jinja' %} -extern {{ name }} const {{ name }}{{param.name}}{{ " __deprecated" if param.deprecated and param.deprecated is sameas true }}; +{% if deprecated or param.deprecated -%} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} +extern {{ name }} const {{ name }}{{param.name}}{{ " __deprecated" if param.deprecated and param.deprecated }}; +{% if deprecated or param.deprecated -%} +#pragma clang diagnostic pop +{%- endif %} {% endfor -%} {% endblock -%} diff --git a/generator/templates/functions/template.m b/generator/templates/functions/template.m index 6b23240ae..f8d1bc59a 100644 --- a/generator/templates/functions/template.m +++ b/generator/templates/functions/template.m @@ -10,8 +10,9 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (instancetype)init { self = [super initWithName:SDLRPCFunctionName{{origin}}]; - if (!self) { return nil; } - + if (!self) { + return nil; + } return self; } #pragma clang diagnostic pop diff --git a/generator/test/test_functions.py b/generator/test/test_functions.py index af858d5b8..a271fa23a 100644 --- a/generator/test/test_functions.py +++ b/generator/test/test_functions.py @@ -43,8 +43,10 @@ def setUp(self): function_names='SDLRPCFunctionNames', parameter_names='SDLRPCParameterNames') - names = ('FileType', 'Language', 'SyncMsgVersion', 'TemplateColorScheme', 'TTSChunk', 'Choice') - self.producer = FunctionsProducer(paths, names, key_words) + enum_names = ('FileType', 'Language',) + struct_names = ('SyncMsgVersion', 'TemplateColorScheme', 'TTSChunk', 'Choice') + + self.producer = FunctionsProducer(paths, enum_names, struct_names, key_words) def test_process_function_name(self): """ diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py index 9ddd8fef9..beb9e35c5 100644 --- a/generator/test/test_structs.py +++ b/generator/test/test_structs.py @@ -29,7 +29,7 @@ def setUp(self): self.maxDiff = None key_words = Generator().get_key_words() - self.producer = StructsProducer('SDLRPCStruct', ['Image'], key_words) + self.producer = StructsProducer('SDLRPCStruct', enum_names=(), struct_names=['Image'], key_words=key_words) def test_CloudAppProperties(self): """ diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index 4719a48e6..d1550c8c2 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -24,9 +24,9 @@ class InterfaceProducerCommon(ABC): All Enums/Structs/Functions Producer are inherited from this class and using features of it """ - def __init__(self, names=(), key_words=()): + def __init__(self, enum_names=(), struct_names=(), key_words=()): self.logger = logging.getLogger(self.__class__.__name__) - self.names = list(map(lambda e: self.replace_sync(e), names)) + self.struct_names = tuple(map(lambda e: self.replace_sync(e), struct_names)) self.key_words = key_words self.param_named = namedtuple('param_named', 'origin constructor_argument constructor_prefix deprecated mandatory since ' @@ -34,6 +34,7 @@ def __init__(self, names=(), key_words=()): 'constructor_argument_override') self.constructor_named = namedtuple('constructor', 'init self arguments all deprecated') self.argument_named = namedtuple('argument', 'origin constructor_argument variable deprecated') + self.names = self.struct_names + tuple(map(lambda e: self.replace_sync(e), enum_names)) @property @abstractmethod @@ -110,7 +111,7 @@ def extract_imports(self, param: Param, imports: dict): else: type_origin, kind = self.evaluate_import(param.param_type) - if type_origin and (type_origin in self.names or self.title(type_origin) in self.names): + if type_origin and any(map(lambda n: type_origin.lower() in n.lower(), self.names)): name = 'SDL' + type_origin imports['.h'][kind].add(name) imports['.m'].add(name) @@ -170,14 +171,14 @@ def nullable(type_native: str, mandatory: bool) -> str: return '' return 'nullable ' - @staticmethod - def wrap(item): + def wrap(self, item): """ Used for wrapping appropriate initiator (constructor) parameter with '@({})' :param item: named tup[le with initiator (constructor) parameter :return: wrapped parameter """ - if re.match(r'\w*Int\d*|BOOL|float|double', item.type_native): + if re.match(r'\w*Int\d+|BOOL|float|double', item.type_native) or \ + any(map(lambda n: item.type_native.lower() in n.lower(), self.struct_names)): return '@({})'.format(item.constructor_argument) return item.constructor_argument @@ -204,7 +205,7 @@ def extract_constructor(self, data: list, mandatory: bool) -> dict: param.type_native.strip(), param.constructor_argument) init.append(init_str) _self = True if 'functions' in self.__class__.__name__.lower() and mandatory else '' - return {'init': ' '.join(init), 'self': _self, 'arguments': arguments, 'all': arguments, 'deprecated': False} + return {'init': ' '.join(init), 'self': _self, 'arguments': arguments, 'all': arguments} def extract_constructors(self, data: dict) -> tuple: """ @@ -214,6 +215,7 @@ def extract_constructors(self, data: dict) -> tuple: """ mandatory = [] not_mandatory = [] + deprecated = any([m.deprecated for m in data.values() if getattr(m, 'deprecated', False)]) for param in data.values(): if param.mandatory: mandatory.append(param) @@ -223,15 +225,16 @@ def extract_constructors(self, data: dict) -> tuple: result = [] if mandatory: mandatory = self.extract_constructor(mandatory, True) + mandatory['deprecated'] = deprecated else: mandatory = OrderedDict() if not_mandatory: not_mandatory = self.extract_constructor(not_mandatory, False) + not_mandatory['deprecated'] = deprecated if mandatory: not_mandatory['init'] = '{} {}'.format(mandatory['init'], self.minimize_first(not_mandatory['init'])) not_mandatory['all'] = mandatory['arguments'] + not_mandatory['arguments'] - not_mandatory['deprecated'] = any([m.deprecated for m in mandatory if hasattr(m, 'deprecated')]) not_mandatory['self'] = re.sub(r'([\w\d]+:)\([\w\d\s<>*]*\)([\w\d]+\s*)', r'\1\2', mandatory['init']) result.append(self.constructor_named(**not_mandatory)) diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py index 8eaf5fbc4..f858a8267 100644 --- a/generator/transformers/functions_producer.py +++ b/generator/transformers/functions_producer.py @@ -14,8 +14,8 @@ class FunctionsProducer(InterfaceProducerCommon): Functions transformer """ - def __init__(self, paths, names, key_words): - super(FunctionsProducer, self).__init__(names=names, key_words=key_words) + def __init__(self, paths, enum_names, struct_names, key_words): + super(FunctionsProducer, self).__init__(enum_names=enum_names, struct_names=struct_names, key_words=key_words) self._container_name = 'params' self.request_class = paths.request_class self.response_class = paths.response_class diff --git a/generator/transformers/structs_producer.py b/generator/transformers/structs_producer.py index 154d34f81..2c9e0ec64 100644 --- a/generator/transformers/structs_producer.py +++ b/generator/transformers/structs_producer.py @@ -14,8 +14,8 @@ class StructsProducer(InterfaceProducerCommon): Structs transformer """ - def __init__(self, struct_class, enum_names, key_words): - super(StructsProducer, self).__init__(names=enum_names, key_words=key_words) + def __init__(self, struct_class, enum_names, struct_names, key_words): + super(StructsProducer, self).__init__(enum_names=enum_names, struct_names=struct_names, key_words=key_words) self._container_name = 'members' self.struct_class = struct_class self.logger = logging.getLogger(self.__class__.__name__) From 8d538469711fe0690157629c5f8d0c361b29cdff Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Wed, 1 Apr 2020 18:21:49 +0200 Subject: [PATCH 24/28] echanced deprecated view --- generator/templates/base_struct_function.m | 28 +++++++-------- generator/templates/enums/template.h | 16 ++++++--- generator/templates/enums/template.m | 16 ++++++++- generator/test/test_enums.py | 16 +++++---- generator/transformers/common_producer.py | 36 +++++++++++--------- generator/transformers/enums_producer.py | 31 ++++++++++------- generator/transformers/functions_producer.py | 4 +-- generator/transformers/structs_producer.py | 2 +- 8 files changed, 90 insertions(+), 59 deletions(-) diff --git a/generator/templates/base_struct_function.m b/generator/templates/base_struct_function.m index 4c991b1ad..8e98fddd4 100644 --- a/generator/templates/base_struct_function.m +++ b/generator/templates/base_struct_function.m @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN {% if deprecated %} #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wdeprecated-implementations" {%- endif %} @implementation {{name}} {%- if deprecated %} @@ -25,9 +25,6 @@ @implementation {{name}} #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} - (instancetype)initWith{{c.init}} { -{%- if deprecated or c.deprecated %} -#pragma clang diagnostic pop -{%- endif %} self = [{{ 'self' if c.self else 'super' }} init{{ 'With' + c.self if c.self and c.self is string }}]; if (!self) { return nil; @@ -37,32 +34,35 @@ - (instancetype)initWith{{c.init}} { {%- endfor %} return self; } +{%- if deprecated or c.deprecated %} +#pragma clang diagnostic pop +{%- endif %} {% endfor -%} {% endblock -%} {%- block methods %} -{%- for p in params %} -{%- if deprecated or p.deprecated %} +{%- for param in params %} +{%- if deprecated or param.deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} -- (void)set{{p.origin|title}}:({{'nullable ' if not p.mandatory}}{{p.type_generic}}{{p.type_sdl|trim}}){{p.origin}} { -{%- if deprecated or p.deprecated %} +- (void)set{{param.origin|title}}:({{'nullable ' if not param.mandatory}}{{param.type_generic}}{{param.type_sdl|trim}}){{param.origin}} { +{%- if deprecated or param.deprecated %} #pragma clang diagnostic pop {%- endif %} - [self.{{parameters_store}} sdl_setObject:{{p.origin}} forName:SDLRPCParameterName{{p.method_suffix}}]; + [self.{{parameters_store}} sdl_setObject:{{param.origin}} forName:SDLRPCParameterName{{param.method_suffix}}]; } -{% if deprecated or p.deprecated %} +{% if deprecated or param.deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} -- ({{'nullable ' if not p.mandatory}}{{p.type_generic}}{{p.type_sdl|trim}}){{p.origin}} { -{%- if deprecated or p.deprecated %} +- ({{'nullable ' if not param.mandatory}}{{param.type_generic}}{{param.type_sdl|trim}}){{param.origin}} { +{%- if deprecated or param.deprecated %} #pragma clang diagnostic pop {%- endif %} - {% if p.mandatory -%} + {% if param.mandatory -%} NSError *error = nil; {% endif -%} - return [self.{{parameters_store}} sdl_{{p.for_name}}ForName:SDLRPCParameterName{{p.method_suffix}}{{' ofClass:'+p.of_class if p.of_class}} error:{{'&error' if p.mandatory else 'nil'}}]; + return [self.{{parameters_store}} sdl_{{param.for_name}}ForName:SDLRPCParameterName{{param.method_suffix}}{{' ofClass:'+param.of_class if param.of_class}} error:{{'&error' if param.mandatory else 'nil'}}]; } {% endfor %} {%- endblock %} diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h index a0dea105a..e3d6927dc 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h @@ -8,15 +8,21 @@ {%- block body %} {% include 'description.jinja' %} typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; -{% for param in params %} -{%- include 'description_param.jinja' %} -{% if deprecated or param.deprecated -%} +{% if deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{% endif %} +{%- for param in params %} +{%- include 'description_param.jinja' %}{% if param.deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} extern {{ name }} const {{ name }}{{param.name}}{{ " __deprecated" if param.deprecated and param.deprecated }}; -{% if deprecated or param.deprecated -%} +{% if param.deprecated -%} #pragma clang diagnostic pop -{%- endif %} +{%- endif -%} {% endfor -%} +{%- if deprecated %} +#pragma clang diagnostic pop +{%- endif %} {% endblock -%} diff --git a/generator/templates/enums/template.m b/generator/templates/enums/template.m index 5a83cb680..dc13f4611 100644 --- a/generator/templates/enums/template.m +++ b/generator/templates/enums/template.m @@ -5,7 +5,21 @@ {% if add_typedef %} typedef SDLEnum {{name}} SDL_SWIFT_ENUM; {% endif -%} +{%- if deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} {%- for param in params %} +{%- if param.deprecated %} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +{%- endif %} {{ name }} const {{ name }}{{param.name}} = @"{{param.origin}}"; -{%- endfor %} +{%- if param.deprecated %} +#pragma clang diagnostic pop +{% endif %} +{%- endfor -%} +{%- if deprecated %} +#pragma clang diagnostic pop +{%- endif %} {% endblock -%} diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py index b3a5a7f42..d7e14786a 100644 --- a/generator/test/test_enums.py +++ b/generator/test/test_enums.py @@ -45,11 +45,11 @@ def test_FunctionID(self): expected['name'] = 'SDLFunctionID' expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} expected['params'] = ( - self.producer.param_named(description=[], name='Reserved', origin='RESERVED', since=None), + self.producer.param_named(description=[], name='Reserved', origin='RESERVED', since=None, deprecated=False), self.producer.param_named(description=[], name='RegisterAppInterface', origin='RegisterAppInterfaceID', - since=None), + since=None, deprecated=False), self.producer.param_named(description=[], name='PerformAudioPassThru', origin='PerformAudioPassThruID', - since=None),) + since=None, deprecated=False),) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) @@ -71,11 +71,13 @@ def test_TextFieldName(self): expected['name'] = 'SDLTextFieldName' expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} expected['params'] = ( - self.producer.param_named(description=[], name='SuccessParam', origin='SUCCESS_PARAM', since=None), - self.producer.param_named(description=[], name='MainField1', origin='mainField1', since=None), - self.producer.param_named(description=[], name='H264', origin='H264', since=None), + self.producer.param_named(description=[], name='SuccessParam', origin='SUCCESS', since=None, + deprecated=False), + self.producer.param_named(description=[], name='MainField1', origin='mainField1', since=None, + deprecated=False), + self.producer.param_named(description=[], name='H264', origin='H264', since=None, deprecated=False), self.producer.param_named(description=[], name='UnsupportedRequest', origin='UNSUPPORTED_REQUEST', - since=None)) + since=None, deprecated=False)) actual = self.producer.transform(item) self.assertDictEqual(expected, actual) diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index d1550c8c2..987ed5934 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -26,7 +26,7 @@ class InterfaceProducerCommon(ABC): def __init__(self, enum_names=(), struct_names=(), key_words=()): self.logger = logging.getLogger(self.__class__.__name__) - self.struct_names = tuple(map(lambda e: self.replace_sync(e), struct_names)) + self.struct_names = tuple(map(lambda e: self._replace_sync(e), struct_names)) self.key_words = key_words self.param_named = namedtuple('param_named', 'origin constructor_argument constructor_prefix deprecated mandatory since ' @@ -34,7 +34,7 @@ def __init__(self, enum_names=(), struct_names=(), key_words=()): 'constructor_argument_override') self.constructor_named = namedtuple('constructor', 'init self arguments all deprecated') self.argument_named = namedtuple('argument', 'origin constructor_argument variable deprecated') - self.names = self.struct_names + tuple(map(lambda e: self.replace_sync(e), enum_names)) + self.names = self.struct_names + tuple(map(lambda e: self._replace_sync(e), enum_names)) @property @abstractmethod @@ -61,10 +61,7 @@ def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: render['params'] = OrderedDict() for param in getattr(item, self.container_name).values(): - # if param.name.lower() == 'id': - # param.name = self.minimize_first(item.name) + self.title(param.name) - param.name = self.replace_keywords(param.name) - render['params'][param.name] = self.extract_param(param) + render['params'][param.name] = self.extract_param(param, item.name) if isinstance(item, (Struct, Function)): self.extract_imports(param, render['imports']) @@ -74,22 +71,26 @@ def transform(self, item: (Enum, Function, Struct), render: dict) -> dict: render['params'] = tuple(render['params'].values()) return render - def replace_keywords(self, name: str = '') -> str: + def _replace_keywords(self, name: str) -> str: + origin = name + if name.isupper(): + name += '_PARAM' + else: + name += 'Param' + self.logger.debug('Replacing %s with %s', origin, 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 name.casefold() in 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) + name = self._replace_keywords(name) + return self._replace_sync(name) @staticmethod - def replace_sync(name): + def _replace_sync(name): """ :param name: string with item name :return: string with replaced 'sync' to 'Sdl' @@ -124,7 +125,7 @@ def evaluate_import(self, element): :return: tuple with element.name, type(element).__name__.lower() """ if isinstance(element, (Struct, Enum)): - return self.replace_sync(element.name), type(element).__name__.lower() + return self._replace_sync(element.name), type(element).__name__.lower() return None, None @staticmethod @@ -324,12 +325,13 @@ def param_origin_change(name) -> dict: 'constructor_prefix': InterfaceProducerCommon.title(name), 'method_suffix': InterfaceProducerCommon.title(name)} - def extract_param(self, param: Param): + def extract_param(self, param: Param, item_name: str): """ Preparing self.param_named with prepared params :param param: Param from initial Model :return: self.param_named with prepared params """ + param.name = self.replace_keywords(param.name) data = {'constructor_argument_override': None, 'description': self.extract_description(param.description), 'since': param.since, diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py index cd44a640d..69797f772 100644 --- a/generator/transformers/enums_producer.py +++ b/generator/transformers/enums_producer.py @@ -1,6 +1,7 @@ """ Enums transformer """ +import json import logging import re from collections import namedtuple, OrderedDict @@ -20,7 +21,8 @@ def __init__(self, enum_class, key_words): self._container_name = 'elements' self.enum_class = enum_class self.logger = logging.getLogger(self.__class__.__name__) - self.param_named = namedtuple('param_named', 'origin description name since') + self.param_named = namedtuple('param_named', 'origin description name since deprecated') + self._item_name = None @property def container_name(self): @@ -34,7 +36,7 @@ def transform(self, item: Enum, render: dict = None) -> dict: :param render: empty dictionary, present in parameter for code consistency :return: dictionary which going to be applied to Jinja2 template """ - item.name = self.replace_sync(item.name) + item.name = self._replace_sync(item.name) name = 'SDL{}{}'.format(item.name[:1].upper(), item.name[1:]) tmp = {self.enum_class} imports = {'.h': tmp, '.m': tmp} @@ -46,29 +48,34 @@ def transform(self, item: Enum, render: dict = None) -> dict: super(EnumsProducer, self).transform(item, render) return render - def extract_param(self, param: EnumElement): + def extract_param(self, param: EnumElement, item_name: str): """ Preparing self.param_named with prepared params :param param: EnumElement from initial Model + :param item_name: :return: self.param_named with prepared params """ - data = {'origin': param.name, 'description': self.extract_description(param.description, 113), - 'since': param.since} - + data = {'origin': param.name, + 'description': self.extract_description(param.description), + 'since': param.since, + 'deprecated': json.loads(param.deprecated.lower()) if param.deprecated else False} + name = None if re.match(r'^[A-Z]{1,2}\d|\d[A-Z]{1,2}$', param.name): - data['name'] = param.name + name = param.name elif re.match(r'(^[a-z\d]+$|^[A-Z\d]+$)', param.name): - data['name'] = param.name.title() + name = param.name.title() elif re.match(r'^(?=\w*[a-z])(?=\w*[A-Z])\w+$', param.name): if param.name.endswith('ID'): - data['name'] = param.name[:-2] + name = param.name[:-2] else: - data['name'] = param.name[:1].upper() + param.name[1:] + name = param.name[:1].upper() + param.name[1:] elif re.match(r'^(?=\w*?[a-zA-Z])(?=\w*?[_-])(?=[0-9])?.*$', param.name): name = [] for item in re.split('[_-]', param.name): if re.match(r'^[A-Z\d]+$', item): name.append(item.title()) - data['name'] = ''.join(name) - + name = ''.join(name) + if any(re.search(r'^(sdl)?({})?{}$'.format(item_name.casefold(), name.casefold()), k) for k in self.key_words): + name = self._replace_keywords(name) + data['name'] = name return self.param_named(**data) diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py index f858a8267..fcab84a14 100644 --- a/generator/transformers/functions_producer.py +++ b/generator/transformers/functions_producer.py @@ -38,7 +38,7 @@ def transform(self, item: Function, render: dict = None) -> dict: :return: dictionary which going to be applied to Jinja2 template """ list(map(item.params.__delitem__, filter(item.params.__contains__, ['success', 'resultCode', 'info']))) - item.name = self.replace_sync(item.name) + item.name = self._replace_sync(item.name) name = 'SDL' + item.name imports = {'.h': {'enum': set(), 'struct': set()}, '.m': set()} extends_class = None @@ -86,7 +86,7 @@ def evaluate(self, element) -> dict: :return: dictionary with evaluated part of output collection """ origin = element.name - name = self.replace_sync(element.name) + name = self._replace_sync(element.name) # if isinstance(element.param_type, (Integer, Float, Boolean, String)): return {name: self.common_names(**{ 'name': self.title(name), diff --git a/generator/transformers/structs_producer.py b/generator/transformers/structs_producer.py index 2c9e0ec64..b25d86230 100644 --- a/generator/transformers/structs_producer.py +++ b/generator/transformers/structs_producer.py @@ -32,7 +32,7 @@ def transform(self, item: Struct, render: dict = None) -> dict: :param render: dictionary with pre filled entries, which going to be filled/changed by reference :return: dictionary which going to be applied to Jinja2 template """ - item.name = self.replace_sync(item.name) + item.name = self._replace_sync(item.name) name = 'SDL' + item.name imports = {'.h': {'enum': set(), 'struct': set()}, '.m': set()} imports['.h']['enum'].add(self.struct_class) From e1312154fd5b0f8e79e27727ec4f95f8313211ca Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Thu, 2 Apr 2020 23:26:07 +0200 Subject: [PATCH 25/28] update regexp for keywords --- generator/transformers/enums_producer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py index 69797f772..d41cd739d 100644 --- a/generator/transformers/enums_producer.py +++ b/generator/transformers/enums_producer.py @@ -75,7 +75,7 @@ def extract_param(self, param: EnumElement, item_name: str): if re.match(r'^[A-Z\d]+$', item): name.append(item.title()) name = ''.join(name) - if any(re.search(r'^(sdl)?({})?{}$'.format(item_name.casefold(), name.casefold()), k) for k in self.key_words): + if any(re.search(r'^(sdl)?({}){}$'.format(item_name.casefold(), name.casefold()), k) for k in self.key_words): name = self._replace_keywords(name) data['name'] = name return self.param_named(**data) From 44ed1a6150fb129575b80105d8b92a0f840cf35e Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Thu, 2 Apr 2020 23:27:17 +0200 Subject: [PATCH 26/28] update regex in test --- generator/test/test_enums.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py index d7e14786a..22650ce67 100644 --- a/generator/test/test_enums.py +++ b/generator/test/test_enums.py @@ -71,7 +71,7 @@ def test_TextFieldName(self): expected['name'] = 'SDLTextFieldName' expected['imports'] = {'.h': {'SDLEnum'}, '.m': {'SDLEnum'}} expected['params'] = ( - self.producer.param_named(description=[], name='SuccessParam', origin='SUCCESS', since=None, + self.producer.param_named(description=[], name='Success', origin='SUCCESS', since=None, deprecated=False), self.producer.param_named(description=[], name='MainField1', origin='mainField1', since=None, deprecated=False), From a179e65851c4c239d5bef67a0f3ca591dd235dec Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Fri, 10 Apr 2020 17:02:59 +0200 Subject: [PATCH 27/28] Apply suggestions from code review --- generator/README.md | 27 +++++++++++++++++++ generator/generator.py | 4 +-- ...onNames.h => SDLRPCFunctionNames.h.jinja2} | 2 +- ...onNames.m => SDLRPCFunctionNames.m.jinja2} | 2 +- ...rNames.h => SDLRPCParameterNames.h.jinja2} | 2 +- ...rNames.m => SDLRPCParameterNames.m.jinja2} | 2 +- ...nction.h => base_struct_function.h.jinja2} | 6 ++--- ...nction.m => base_struct_function.m.jinja2} | 2 +- .../{copyright.txt => copyright.jinja2} | 0 .../{description.jinja => description.jinja2} | 0 ...n_param.jinja => description_param.jinja2} | 0 .../enums/{template.h => template.h.jinja2} | 6 ++--- .../enums/{template.m => template.m.jinja2} | 2 +- .../{template.h => template.h.jinja2} | 2 +- .../{template.m => template.m.jinja2} | 2 +- .../structs/{template.h => template.h.jinja2} | 2 +- .../structs/{template.m => template.m.jinja2} | 2 +- generator/transformers/common_producer.py | 15 +++++------ 18 files changed, 52 insertions(+), 26 deletions(-) rename generator/templates/{SDLRPCFunctionNames.h => SDLRPCFunctionNames.h.jinja2} (95%) rename generator/templates/{SDLRPCFunctionNames.m => SDLRPCFunctionNames.m.jinja2} (84%) rename generator/templates/{SDLRPCParameterNames.h => SDLRPCParameterNames.h.jinja2} (83%) rename generator/templates/{SDLRPCParameterNames.m => SDLRPCParameterNames.m.jinja2} (82%) rename generator/templates/{base_struct_function.h => base_struct_function.h.jinja2} (93%) rename generator/templates/{base_struct_function.m => base_struct_function.m.jinja2} (98%) rename generator/templates/{copyright.txt => copyright.jinja2} (100%) rename generator/templates/{description.jinja => description.jinja2} (100%) rename generator/templates/{description_param.jinja => description_param.jinja2} (100%) rename generator/templates/enums/{template.h => template.h.jinja2} (84%) rename generator/templates/enums/{template.m => template.m.jinja2} (94%) rename generator/templates/functions/{template.h => template.h.jinja2} (64%) rename generator/templates/functions/{template.m => template.m.jinja2} (92%) rename generator/templates/structs/{template.h => template.h.jinja2} (52%) rename generator/templates/structs/{template.m => template.m.jinja2} (61%) diff --git a/generator/README.md b/generator/README.md index 216e730a7..b6ec2a0b7 100644 --- a/generator/README.md +++ b/generator/README.md @@ -790,6 +790,33 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END ``` +## Unit tests of Generator + +After you made any changes in python RPC generator to avoid affecting code logic you should run Unit tests as follow: + +```shell script +$ cd sdl_ios +$ python3 generator/test/runner.py +``` + +In case of successful execution of all Unit tests in output you will see the results as follow: + +```shell script +Ran 12 tests in 0.464s + +OK +``` + +As well you can check coverage by Unit tests of python RPC generator as follow: + +```shell script +coverage run generator/test/runner.py +coverage html +``` + +after the you can check the report in `htmlcov/index.html` + + ## Other Utilities ### Generator diff --git a/generator/generator.py b/generator/generator.py index 6192b6816..17f0be091 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -343,7 +343,7 @@ async def process_main(self, skip: bool, overwrite: bool, items: dict, transform data = render.copy() data['imports'] = data['imports'][extension] file_with_suffix = file.with_suffix(extension) - templates = ['{}s/template{}'.format(type(item).__name__.lower(), extension)] + templates = ['{}s/template{}.jinja2'.format(type(item).__name__.lower(), extension)] if 'template' in data: templates.insert(0, data['template'] + extension) tasks.append(self.process_common(skip, overwrite, file_with_suffix, data, templates)) @@ -372,7 +372,7 @@ async def process_function_name(self, skip: bool, overwrite: bool, functions: di self.logger.error('No "data" for %s', name) continue for extension in ('.h', '.m'): - templates = [name + extension] + templates = ['{}{}.jinja2'.format(name, extension)] file_with_suffix = file.with_suffix(extension) tasks.append(self.process_common(skip, overwrite, file_with_suffix, data, templates)) diff --git a/generator/templates/SDLRPCFunctionNames.h b/generator/templates/SDLRPCFunctionNames.h.jinja2 similarity index 95% rename from generator/templates/SDLRPCFunctionNames.h rename to generator/templates/SDLRPCFunctionNames.h.jinja2 index 3126c8169..5e13b735b 100644 --- a/generator/templates/SDLRPCFunctionNames.h +++ b/generator/templates/SDLRPCFunctionNames.h.jinja2 @@ -1,4 +1,4 @@ -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} // SDLRPCFunctionNames.h #import "SDLEnum.h" diff --git a/generator/templates/SDLRPCFunctionNames.m b/generator/templates/SDLRPCFunctionNames.m.jinja2 similarity index 84% rename from generator/templates/SDLRPCFunctionNames.m rename to generator/templates/SDLRPCFunctionNames.m.jinja2 index 2ae5f21a6..c7dc5a374 100644 --- a/generator/templates/SDLRPCFunctionNames.m +++ b/generator/templates/SDLRPCFunctionNames.m.jinja2 @@ -1,4 +1,4 @@ -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} // SDLRPCFunctionNames.m #import "SDLRPCFunctionNames.h" diff --git a/generator/templates/SDLRPCParameterNames.h b/generator/templates/SDLRPCParameterNames.h.jinja2 similarity index 83% rename from generator/templates/SDLRPCParameterNames.h rename to generator/templates/SDLRPCParameterNames.h.jinja2 index 4a00ce029..7a5bbb746 100644 --- a/generator/templates/SDLRPCParameterNames.h +++ b/generator/templates/SDLRPCParameterNames.h.jinja2 @@ -1,4 +1,4 @@ -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} // SDLRPCParameterNames.h #import diff --git a/generator/templates/SDLRPCParameterNames.m b/generator/templates/SDLRPCParameterNames.m.jinja2 similarity index 82% rename from generator/templates/SDLRPCParameterNames.m rename to generator/templates/SDLRPCParameterNames.m.jinja2 index ae6bdace7..b29a1705c 100644 --- a/generator/templates/SDLRPCParameterNames.m +++ b/generator/templates/SDLRPCParameterNames.m.jinja2 @@ -1,4 +1,4 @@ -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} // SDLRPCParameterNames.h #import "NSMutableDictionary+Store.h" diff --git a/generator/templates/base_struct_function.h b/generator/templates/base_struct_function.h.jinja2 similarity index 93% rename from generator/templates/base_struct_function.h rename to generator/templates/base_struct_function.h.jinja2 index a0c697f64..cd1c23d6b 100644 --- a/generator/templates/base_struct_function.h +++ b/generator/templates/base_struct_function.h.jinja2 @@ -1,6 +1,6 @@ {#- To avoid code duplication was crated this parent file, which contain common part used in: "templates/functions/template.h" and "templates/structs/template.h". -#} -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} {% block imports %} {%- for import in imports.enum %} #import "{{import}}.h" @@ -13,7 +13,7 @@ {%- endblock %} NS_ASSUME_NONNULL_BEGIN -{% include 'description.jinja' %} +{% include 'description.jinja2' %} @interface {{name}} : {{extends_class}}{{ending}} {%- block constructors %} {% for c in constructors %} @@ -41,7 +41,7 @@ NS_ASSUME_NONNULL_BEGIN {%- endblock -%} {%- block methods %} {%- for param in params %} -{%- include 'description_param.jinja' %} +{%- include 'description_param.jinja2' %} {%- if deprecated or param.deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" diff --git a/generator/templates/base_struct_function.m b/generator/templates/base_struct_function.m.jinja2 similarity index 98% rename from generator/templates/base_struct_function.m rename to generator/templates/base_struct_function.m.jinja2 index 8e98fddd4..30bb2e976 100644 --- a/generator/templates/base_struct_function.m +++ b/generator/templates/base_struct_function.m.jinja2 @@ -1,6 +1,6 @@ {#- To avoid code duplication was crated this parent file, which contain common part used in: "templates/functions/template.m" and "templates/structs/template.m". -#} -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} {%- block imports %} #import "{{name}}.h" #import "NSMutableDictionary+Store.h" diff --git a/generator/templates/copyright.txt b/generator/templates/copyright.jinja2 similarity index 100% rename from generator/templates/copyright.txt rename to generator/templates/copyright.jinja2 diff --git a/generator/templates/description.jinja b/generator/templates/description.jinja2 similarity index 100% rename from generator/templates/description.jinja rename to generator/templates/description.jinja2 diff --git a/generator/templates/description_param.jinja b/generator/templates/description_param.jinja2 similarity index 100% rename from generator/templates/description_param.jinja rename to generator/templates/description_param.jinja2 diff --git a/generator/templates/enums/template.h b/generator/templates/enums/template.h.jinja2 similarity index 84% rename from generator/templates/enums/template.h rename to generator/templates/enums/template.h.jinja2 index e3d6927dc..edba9f0bf 100644 --- a/generator/templates/enums/template.h +++ b/generator/templates/enums/template.h.jinja2 @@ -1,19 +1,19 @@ {#- String based enum -#} -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} {% block imports -%} {%- for import in imports %} #import "{{import}}.h" {%- endfor %} {%- endblock -%} {%- block body %} -{% include 'description.jinja' %} +{% include 'description.jinja2' %} typedef SDLEnum {{ name }} SDL_SWIFT_ENUM{{ending}}; {% if deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {% endif %} {%- for param in params %} -{%- include 'description_param.jinja' %}{% if param.deprecated %} +{%- include 'description_param.jinja2' %}{% if param.deprecated %} #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" {%- endif %} diff --git a/generator/templates/enums/template.m b/generator/templates/enums/template.m.jinja2 similarity index 94% rename from generator/templates/enums/template.m rename to generator/templates/enums/template.m.jinja2 index dc13f4611..0e8ca0b2b 100644 --- a/generator/templates/enums/template.m +++ b/generator/templates/enums/template.m.jinja2 @@ -1,4 +1,4 @@ -{% include 'copyright.txt' %} +{% include 'copyright.jinja2' %} #import "{{name}}.h" {%- block body %} diff --git a/generator/templates/functions/template.h b/generator/templates/functions/template.h.jinja2 similarity index 64% rename from generator/templates/functions/template.h rename to generator/templates/functions/template.h.jinja2 index b2301dba3..28e3fd0be 100644 --- a/generator/templates/functions/template.h +++ b/generator/templates/functions/template.h.jinja2 @@ -1,2 +1,2 @@ {#- This template creates RPC requests, responses, and notification .h files -#} -{% extends "base_struct_function.h" %} \ No newline at end of file +{% extends "base_struct_function.h.jinja2" %} \ No newline at end of file diff --git a/generator/templates/functions/template.m b/generator/templates/functions/template.m.jinja2 similarity index 92% rename from generator/templates/functions/template.m rename to generator/templates/functions/template.m.jinja2 index f8d1bc59a..9809b8df2 100644 --- a/generator/templates/functions/template.m +++ b/generator/templates/functions/template.m.jinja2 @@ -1,5 +1,5 @@ {#- This template creates RPC requests, responses, and notification .m files -#} -{% extends "base_struct_function.m" %} +{% extends "base_struct_function.m.jinja2" %} {% block imports %} {{super()}} #import "SDLRPCFunctionNames.h" diff --git a/generator/templates/structs/template.h b/generator/templates/structs/template.h.jinja2 similarity index 52% rename from generator/templates/structs/template.h rename to generator/templates/structs/template.h.jinja2 index af44cb710..f57d273ca 100644 --- a/generator/templates/structs/template.h +++ b/generator/templates/structs/template.h.jinja2 @@ -1,2 +1,2 @@ {#- This template creates RPC struct .h files -#} -{% extends "base_struct_function.h" %} \ No newline at end of file +{% extends "base_struct_function.h.jinja2" %} \ No newline at end of file diff --git a/generator/templates/structs/template.m b/generator/templates/structs/template.m.jinja2 similarity index 61% rename from generator/templates/structs/template.m rename to generator/templates/structs/template.m.jinja2 index 0d4c1b760..488b076af 100644 --- a/generator/templates/structs/template.m +++ b/generator/templates/structs/template.m.jinja2 @@ -1,5 +1,5 @@ {#- This template creates RPC struct .m files -#} -{% extends "base_struct_function.m" %} +{% extends "base_struct_function.m.jinja2" %} {% block imports %} {{super()}} #import "SDLRPCParameterNames.h" diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py index 987ed5934..ac7bc6167 100644 --- a/generator/transformers/common_producer.py +++ b/generator/transformers/common_producer.py @@ -172,7 +172,7 @@ def nullable(type_native: str, mandatory: bool) -> str: return '' return 'nullable ' - def wrap(self, item): + def parentheses(self, item): """ Used for wrapping appropriate initiator (constructor) parameter with '@({})' :param item: named tup[le with initiator (constructor) parameter @@ -196,15 +196,14 @@ def extract_constructor(self, data: list, mandatory: bool) -> dict: init = ['{}:({}{}){}'.format(self.title(first.constructor_prefix), self.nullable(first.type_native, mandatory), first.type_native.strip(), first.constructor_argument)] - arguments = [self.argument_named(origin=first.origin, constructor_argument=self.wrap(first), + arguments = [self.argument_named(origin=first.origin, constructor_argument=self.parentheses(first), variable=first.constructor_argument, deprecated=first.deprecated)] for param in data: - arguments.append(self.argument_named(origin=param.origin, constructor_argument=self.wrap(param), + arguments.append(self.argument_named(origin=param.origin, constructor_argument=self.parentheses(param), variable=param.constructor_argument, deprecated=param.deprecated)) - init_str = '{}:({}{}){}'.format(self.minimize_first(param.constructor_prefix), - self.nullable(param.type_native, mandatory), - param.type_native.strip(), param.constructor_argument) - init.append(init_str) + init.append('{}:({}{}){}'.format(self.minimize_first(param.constructor_prefix), + self.nullable(param.type_native, mandatory), + param.type_native.strip(), param.constructor_argument)) _self = True if 'functions' in self.__class__.__name__.lower() and mandatory else '' return {'init': ' '.join(init), 'self': _self, 'arguments': arguments, 'all': arguments} @@ -308,7 +307,7 @@ def extract_type(self, param: Param) -> dict: else: data = self.evaluate_type(param.param_type) - if not param.is_mandatory and re.match(r'\w*Int\d*', data['type_native']): + if not param.is_mandatory and re.match(r'\w*Int\d*|BOOL', data['type_native']): data['type_native'] = data['type_sdl'] return data From 0b761b5a4e205fdc56499ce433768520ae00be3f Mon Sep 17 00:00:00 2001 From: Aleksandr Mishchenko Date: Wed, 15 Apr 2020 12:53:34 +0200 Subject: [PATCH 28/28] use all reserver keywords, instead of using ios only --- generator/generator.py | 11 ++--------- generator/test/test_structs.py | 27 +++++++++++++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/generator/generator.py b/generator/generator.py index 17f0be091..2dda4129f 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -265,15 +265,8 @@ def get_key_words(self, file_name=ROOT.joinpath('rpc_spec/RpcParser/RESERVED_KEY """ content = self.get_file_content(file_name) content = tuple(map(lambda e: re.sub(r'\n', r'', e).strip().casefold(), content)) - try: - start_index = content.index('# ios library') - content = content[start_index + 1:len(content)] - content = tuple(filter(lambda e: not re.search(r'^#+\s+.+|^$', e), content)) - self.logger.debug('key_words: %s', ', '.join(content)) - return content - except (IndexError, ValueError, StopIteration) as error1: - self.logger.error('Error while getting key_words, %s %s', type(error1).__name__, error1) - return [] + content = tuple(filter(lambda e: not re.search(r'^#+\s+.+|^$', e), content)) + return content def get_paths(self, file_name: Path = ROOT.joinpath('paths.ini')): """ diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py index beb9e35c5..61ea23702 100644 --- a/generator/test/test_structs.py +++ b/generator/test/test_structs.py @@ -27,7 +27,7 @@ class StructsProducer. def setUp(self): self.maxDiff = None - key_words = Generator().get_key_words() + key_words = ('value', 'id') self.producer = StructsProducer('SDLRPCStruct', enum_names=(), struct_names=['Image'], key_words=key_words) @@ -36,9 +36,10 @@ def test_CloudAppProperties(self): generator/transformers/common_producer.py 64% generator/transformers/structs_producer.py 100% """ - item = Struct(name='CloudAppProperties', members={ - 'appID': Param(name='appID', param_type=String()) - }) + members = OrderedDict() + members['appID'] = Param(name='appID', param_type=String()) + members['value'] = Param(name='value', param_type=String()) + item = Struct(name='CloudAppProperties', members=members) expected = OrderedDict() expected['origin'] = 'CloudAppProperties' expected['name'] = 'SDLCloudAppProperties' @@ -49,15 +50,25 @@ def test_CloudAppProperties(self): constructor_argument='appID', constructor_argument_override=None, constructor_prefix='AppID', deprecated=False, description=['{"default_value": null, "max_length": null, "min_length": null}'], for_name='object', mandatory=True, method_suffix='AppID', modifier='strong', of_class='NSString.class', - origin='appID', since=None, type_native='NSString *', type_sdl='NSString *'),) + origin='appID', since=None, type_native='NSString *', type_sdl='NSString *'), + self.producer.param_named( + constructor_argument='valueParam', constructor_argument_override=None, constructor_prefix='ValueParam', + deprecated=False, description=['{"default_value": null, "max_length": null, "min_length": null}'], + for_name='object', mandatory=True, method_suffix='ValueParam', modifier='strong', + of_class='NSString.class', origin='valueParam', since=None, type_native='NSString *', + type_sdl='NSString *') + ) argument = [ - self.producer.argument_named(variable='appID', deprecated=False, constructor_argument='appID', - origin='appID')] + self.producer.argument_named( + variable='appID', deprecated=False, constructor_argument='appID', origin='appID'), + self.producer.argument_named( + variable='valueParam', deprecated=False, constructor_argument='valueParam', origin='valueParam') + ] expected['constructors'] = (self.producer.constructor_named( all=argument, arguments=argument, deprecated=False, self='', - init='AppID:(NSString *)appID'),) + init='AppID:(NSString *)appID valueParam:(NSString *)valueParam'),) actual = self.producer.transform(item) self.assertDictEqual(expected, actual)