coalib.bearlib.abstractions.ExternalBearWrap — coala 0.11.0.rc1 documentation

# Source code for coalib.bearlib.abstractions.ExternalBearWrap

    import json
    import inspect
    from functools import partial
    from collections import OrderedDict

    from coalib.bears.LocalBear import LocalBear
    from coala_utils.decorators import enforce_signature
    from coalib.misc.Shell import run_shell_command
    from coalib.results.Result import Result
    from coalib.results.SourceRange import SourceRange
    from coalib.settings.FunctionMetadata import FunctionMetadata

    def _prepare_options(options):
        """
        Checks for illegal options and raises ValueError.

        :param options:
            The options dict that contains user/developer inputs.
        :raises ValueError:
            Raised when illegal options are specified.
        """
        allowed_options = {'executable',
                           'settings'}

        # Check for illegal superfluous options.
        superfluous_options = options.keys() - allowed_options
        if superfluous_options:
            raise ValueError(
                'Invalid keyword arguments provided: ' +
                ', '.join(repr(s) for s in sorted(superfluous_options)))

        if 'settings' not in options:
            options['settings'] = {}

    def _create_wrapper(klass, options):
        NoDefaultValue = object()

        class ExternalBearWrapBase(LocalBear):

            @staticmethod
            def create_arguments():
                """
                This method has to be implemented by the class that uses
                the decorator in order to create the arguments needed for
                the executable.
                """
                return ()

            @classmethod
            def get_executable(cls):
                """
                Returns the executable of this class.

                :return:
                    The executable name.
                """
                return options['executable']

            @staticmethod
            def _normalize_desc(description, setting_type,
                                default_value=NoDefaultValue):
                """
                Normalizes the description of the parameters only if there
                is none provided.

                :param description:
                    The parameter description to be modified in case it is empty.
                :param setting_type:
                    The type of the setting. It is needed to create the final
                    tuple.
                :param default_value:
                    The default value of the setting.
                :return:
                    A value for the OrderedDict in the ``FunctionMetadata`` object.
                """
                if description == '':
                    description = FunctionMetadata.str_nodesc

                if default_value is NoDefaultValue:
                    return (description, setting_type)
                else:
                    return (description + ' ' +
                            FunctionMetadata.str_optional.format(default_value),
                            setting_type, default_value)

            @classmethod
            def get_non_optional_params(cls):
                """
                Fetches the non_optional_params from ``options['settings']``
                and also normalizes their descriptions.

                :return:
                    An OrderedDict that is used to create a
                    ``FunctionMetadata`` object.
                """
                non_optional_params = {}
                for setting_name, description in options['settings'].items():
                    if len(description) == 2:
                        non_optional_params[
                            setting_name] = cls._normalize_desc(description[0],
                                                                description[1])
                return OrderedDict(non_optional_params)

            @classmethod
            def get_optional_params(cls):
                """
                Fetches the optional_params from ``options['settings']``
                and also normalizes their descriptions.

                :return:
                    An OrderedDict that is used to create a
                    ``FunctionMetadata`` object.
                """
                optional_params = {}
                for setting_name, description in options['settings'].items():
                    if len(description) == 3:
                        optional_params[
                            setting_name] = cls._normalize_desc(description[0],
                                                                description[1],
                                                                description[2])
                return OrderedDict(optional_params)

            @classmethod
            def get_metadata(cls):
                metadata = FunctionMetadata(
                    'run',
                    optional_params=cls.get_optional_params(),
                    non_optional_params=cls.get_non_optional_params())
                metadata.desc = inspect.getdoc(cls)
                return metadata

            @classmethod
            def _prepare_settings(cls, settings):
                """
                Adds the optional settings to the settings dict in-place.

                :param settings:
                    The settings dict.
                """
                opt_params = cls.get_optional_params()
                for setting_name, description in opt_params.items():
                    if setting_name not in settings:
                        settings[setting_name] = description[2]

            def parse_output(self, out, filename):
                """
                Parses the output JSON into Result objects.

                :param out:
                    Raw output from the given executable (should be JSON).
                :param filename:
                    The filename of the analyzed file. Needed to
                    create the Result objects.
                :return:
                    An iterator yielding ``Result`` objects.
                """
                output = json.loads(out)

                for result in output['results']:
                    affected_code = tuple(
                        SourceRange.from_values(
                            code_range['file'],
                            code_range['start']['line'],
                            code_range['start'].get('column'),
                            code_range.get('end', {}).get('line'),
                            code_range.get('end', {}).get('column'))
                        for code_range in result['affected_code'])
                    yield Result(
                        origin=result['origin'],
                        message=result['message'],
                        affected_code=affected_code,
                        severity=result.get('severity', 1),
                        debug_msg=result.get('debug_msg', ''),
                        additional_info=result.get('additional_info', ''))

            def run(self, filename, file, **settings):
                self._prepare_settings(settings)
                json_string = json.dumps({'filename': filename,
                                          'file': file,
                                          'settings': settings})

                args = self.create_arguments()
                try:
                    args = tuple(args)
                except TypeError:
                    self.err('The given arguments '
                             '{!r} are not iterable.'.format(args))
                    return

                shell_command = (self.get_executable(),) + args
                out, err = run_shell_command(shell_command, json_string)

                return self.parse_output(out, filename)

        result_klass = type(klass.__name__, (klass, ExternalBearWrapBase), {})
        result_klass.__doc__ = klass.__doc__ or ''
        return result_klass

    @enforce_signature
    def external_bear_wrap(executable: str, **options):

        options['executable'] = executable
        _prepare_options(options)

        return partial(_create_wrapper, options=options)