1 | import json |
||
2 | import inspect |
||
3 | from functools import partial |
||
4 | from collections import OrderedDict |
||
5 | |||
6 | from coalib.bears.LocalBear import LocalBear |
||
7 | from coala_utils.decorators import enforce_signature |
||
8 | from coalib.misc.Shell import run_shell_command |
||
9 | from coalib.results.Diff import Diff |
||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
10 | from coalib.results.Result import Result |
||
11 | from coalib.results.SourceRange import SourceRange |
||
12 | from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY |
||
0 ignored issues
–
show
|
|||
13 | from coalib.settings.FunctionMetadata import FunctionMetadata |
||
14 | |||
15 | |||
16 | def _prepare_options(options): |
||
17 | """ |
||
18 | Checks for illegal options and raises ValueError. |
||
19 | |||
20 | :param options: |
||
21 | The options dict that contains user/developer inputs. |
||
22 | :raises ValueError: |
||
23 | Raised when illegal options are specified. |
||
24 | """ |
||
25 | allowed_options = {"executable", |
||
26 | "settings"} |
||
27 | |||
28 | # Check for illegal superfluous options. |
||
29 | superfluous_options = options.keys() - allowed_options |
||
30 | if superfluous_options: |
||
31 | raise ValueError( |
||
32 | "Invalid keyword arguments provided: " + |
||
33 | ", ".join(repr(s) for s in sorted(superfluous_options))) |
||
34 | |||
35 | if not 'settings' in options: |
||
36 | options['settings'] = {} |
||
37 | |||
38 | |||
39 | def _create_wrapper(klass, options): |
||
40 | NoDefaultValue = object() |
||
41 | |||
42 | class ExternalBearWrapBase(LocalBear): |
||
43 | |||
44 | @staticmethod |
||
45 | def create_arguments(): |
||
46 | """ |
||
47 | This method has to be implemented by the class that uses |
||
48 | the decorator in order to create the arguments needed for |
||
49 | the executable. |
||
50 | """ |
||
51 | return () |
||
52 | |||
53 | @classmethod |
||
54 | def get_executable(cls): |
||
55 | """ |
||
56 | Returns the executable of this class. |
||
57 | |||
58 | :return: |
||
59 | The executable name. |
||
60 | """ |
||
61 | return options["executable"] |
||
62 | |||
63 | @staticmethod |
||
64 | def _normalize_desc(description, setting_type, |
||
65 | default_value=NoDefaultValue): |
||
66 | """ |
||
67 | Normalizes the description of the parameters only if there |
||
68 | is none provided. |
||
69 | |||
70 | :param description: |
||
71 | The parameter description to be modified in case it is empty. |
||
72 | :param setting_type: |
||
73 | The type of the setting. It is needed to create the final |
||
74 | tuple. |
||
75 | :param default_value: |
||
76 | The default value of the setting. |
||
77 | :return: |
||
78 | A value for the OrderedDict in the ``FunctionMetadata`` object. |
||
79 | """ |
||
80 | if description == "": |
||
81 | description = FunctionMetadata.str_nodesc |
||
82 | |||
83 | if default_value is NoDefaultValue: |
||
84 | return (description, setting_type) |
||
85 | else: |
||
86 | return (description + " " + |
||
87 | FunctionMetadata.str_optional.format(default_value), |
||
88 | setting_type, default_value) |
||
89 | |||
90 | @classmethod |
||
91 | def get_non_optional_params(cls): |
||
92 | """ |
||
93 | Fetches the non_optional_params from ``options['settings']`` |
||
94 | and also normalizes their descriptions. |
||
95 | |||
96 | :return: |
||
97 | An OrderedDict that is used to create a |
||
98 | ``FunctionMetadata`` object. |
||
99 | """ |
||
100 | non_optional_params = {} |
||
101 | for setting_name, description in options['settings'].items(): |
||
102 | if len(description) == 2: |
||
103 | non_optional_params[ |
||
104 | setting_name] = cls._normalize_desc(description[0], |
||
105 | description[1]) |
||
106 | return OrderedDict(non_optional_params) |
||
107 | |||
108 | @classmethod |
||
109 | def get_optional_params(cls): |
||
110 | """ |
||
111 | Fetches the optional_params from ``options['settings']`` |
||
112 | and also normalizes their descriptions. |
||
113 | |||
114 | :return: |
||
115 | An OrderedDict that is used to create a |
||
116 | ``FunctionMetadata`` object. |
||
117 | """ |
||
118 | optional_params = {} |
||
119 | for setting_name, description in options['settings'].items(): |
||
120 | if len(description) == 3: |
||
121 | optional_params[ |
||
122 | setting_name] = cls._normalize_desc(description[0], |
||
123 | description[1], |
||
124 | description[2]) |
||
125 | return OrderedDict(optional_params) |
||
126 | |||
127 | @classmethod |
||
128 | def get_metadata(cls): |
||
129 | metadata = FunctionMetadata( |
||
130 | 'run', |
||
131 | optional_params=cls.get_optional_params(), |
||
132 | non_optional_params=cls.get_non_optional_params()) |
||
133 | metadata.desc = inspect.getdoc(cls) |
||
134 | return metadata |
||
135 | |||
136 | @classmethod |
||
137 | def _prepare_settings(cls, settings): |
||
138 | """ |
||
139 | Adds the optional settings to the settings dict in-place. |
||
140 | |||
141 | :param settings: |
||
142 | The settings dict. |
||
143 | """ |
||
144 | opt_params = cls.get_optional_params() |
||
145 | for setting_name, description in opt_params.items(): |
||
146 | if setting_name not in settings: |
||
147 | settings[setting_name] = description[2] |
||
148 | |||
149 | def parse_output(self, out, filename): |
||
0 ignored issues
–
show
This method could be written as a function/class method.
If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example class Foo:
def some_method(self, x, y):
return x + y;
could be written as class Foo:
@classmethod
def some_method(cls, x, y):
return x + y;
![]() |
|||
150 | """ |
||
151 | Parses the output JSON into Result objects. |
||
152 | |||
153 | :param out: |
||
154 | Raw output from the given executable (should be JSON). |
||
155 | :param filename: |
||
156 | The filename of the analyzed file. Needed to |
||
157 | create the Result objects. |
||
158 | :return: |
||
159 | An iterator yielding ``Result`` objects. |
||
160 | """ |
||
161 | output = json.loads(out) |
||
162 | |||
163 | for result in output['results']: |
||
164 | affected_code = tuple( |
||
165 | SourceRange.from_values( |
||
166 | code_range['file'], |
||
167 | code_range['start']['line'], |
||
168 | code_range['start'].get('column'), |
||
169 | code_range.get('end', {}).get('line'), |
||
170 | code_range.get('end', {}).get('column')) |
||
171 | for code_range in result['affected_code']) |
||
172 | yield Result( |
||
173 | origin=result['origin'], |
||
174 | message=result['message'], |
||
175 | affected_code=affected_code, |
||
176 | severity=result.get('severity', 1), |
||
177 | debug_msg=result.get('debug_msg', ""), |
||
178 | additional_info=result.get('additional_info', "")) |
||
179 | |||
180 | def run(self, filename, file, **settings): |
||
181 | self._prepare_settings(settings) |
||
182 | json_string = json.dumps({'filename': filename, |
||
183 | 'file': file, |
||
184 | 'settings': settings}) |
||
185 | |||
186 | args = self.create_arguments() |
||
187 | try: |
||
188 | args = tuple(args) |
||
189 | except TypeError: |
||
190 | self.err("The given arguments " |
||
191 | "{!r} are not iterable.".format(args)) |
||
192 | return |
||
193 | |||
194 | shell_command = (self.get_executable(),) + args |
||
195 | out, err = run_shell_command(shell_command, json_string) |
||
196 | |||
197 | return self.parse_output(out, filename) |
||
198 | |||
199 | result_klass = type(klass.__name__, (klass, ExternalBearWrapBase), {}) |
||
200 | result_klass.__doc__ = klass.__doc__ or "" |
||
201 | return result_klass |
||
202 | |||
203 | |||
204 | @enforce_signature |
||
205 | def external_bear_wrap(executable: str, **options): |
||
206 | |||
207 | options["executable"] = executable |
||
208 | _prepare_options(options) |
||
209 | |||
210 | return partial(_create_wrapper, options=options) |
||
211 |