1 | from termcolor import colored |
||
2 | |||
3 | try: |
||
4 | # This import has side effects and is needed to make input() behave nicely |
||
5 | import readline # pylint: disable=unused-import |
||
6 | except ImportError: # pragma: no cover |
||
7 | pass |
||
8 | import os.path |
||
9 | |||
10 | from pyprint.ConsolePrinter import ConsolePrinter |
||
11 | |||
12 | from coalib.misc.DictUtilities import inverse_dicts |
||
13 | from coalib.bearlib.spacing.SpacingHelper import SpacingHelper |
||
14 | from coalib.results.Result import Result |
||
15 | from coalib.results.result_actions.ApplyPatchAction import ApplyPatchAction |
||
16 | from coalib.results.result_actions.OpenEditorAction import OpenEditorAction |
||
17 | from coalib.results.result_actions.PrintDebugMessageAction import ( |
||
18 | PrintDebugMessageAction) |
||
19 | from coalib.results.result_actions.PrintMoreInfoAction import ( |
||
20 | PrintMoreInfoAction) |
||
21 | from coalib.results.result_actions.ShowPatchAction import ShowPatchAction |
||
22 | from coalib.results.RESULT_SEVERITY import ( |
||
23 | RESULT_SEVERITY, RESULT_SEVERITY_COLORS) |
||
24 | from coalib.settings.Setting import Setting |
||
25 | |||
26 | from pygments import highlight |
||
27 | from pygments.formatters import (TerminalTrueColorFormatter, |
||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
28 | TerminalFormatter) |
||
29 | from pygments.filters import VisibleWhitespaceFilter |
||
30 | from pygments.lexers import TextLexer, get_lexer_for_filename |
||
31 | from pygments.style import Style |
||
32 | from pygments.token import Token |
||
33 | from pygments.util import ClassNotFound |
||
34 | |||
35 | |||
36 | class BackgroundSourceRangeStyle(Style): |
||
37 | styles = { |
||
38 | Token: 'bold bg:#BB4D3E #111' |
||
39 | } |
||
40 | |||
41 | |||
42 | class BackgroundMessageStyle(Style): |
||
43 | styles = { |
||
44 | Token: 'bold bg:#eee #111' |
||
45 | } |
||
46 | |||
47 | |||
48 | def highlight_text(text, lexer=TextLexer(), style=None): |
||
49 | if style: |
||
50 | formatter = TerminalTrueColorFormatter(style=style) |
||
51 | else: |
||
52 | formatter = TerminalTrueColorFormatter() |
||
53 | return highlight(text, lexer, formatter)[:-1] |
||
54 | |||
55 | |||
56 | STR_GET_VAL_FOR_SETTING = ("Please enter a value for the setting \"{}\" ({}) " |
||
57 | "needed by {}: ") |
||
58 | STR_LINE_DOESNT_EXIST = ("The line belonging to the following result " |
||
59 | "cannot be printed because it refers to a line " |
||
60 | "that doesn't seem to exist in the given file.") |
||
61 | STR_PROJECT_WIDE = "Project wide:" |
||
62 | FILE_NAME_COLOR = "blue" |
||
63 | FILE_LINES_COLOR = "blue" |
||
64 | CAPABILITY_COLOR = "green" |
||
65 | HIGHLIGHTED_CODE_COLOR = 'red' |
||
66 | SUCCESS_COLOR = 'green' |
||
67 | REQUIRED_SETTINGS_COLOR = 'green' |
||
68 | CLI_ACTIONS = (OpenEditorAction(), |
||
69 | ApplyPatchAction(), |
||
70 | PrintDebugMessageAction(), |
||
71 | PrintMoreInfoAction(), |
||
72 | ShowPatchAction()) |
||
73 | DIFF_EXCERPT_MAX_SIZE = 4 |
||
74 | |||
75 | |||
76 | def format_lines(lines, line_nr=""): |
||
77 | return '\n'.join("|{:>4}| {}".format(line_nr, line) |
||
78 | for line in lines.rstrip("\n").split('\n')) |
||
79 | |||
80 | |||
81 | def print_section_beginning(console_printer, section): |
||
82 | """ |
||
83 | Will be called after initialization current_section in |
||
84 | begin_section() |
||
85 | |||
86 | :param console_printer: Object to print messages on the console. |
||
87 | :param section: The section that will get executed now. |
||
88 | """ |
||
89 | console_printer.print("Executing section {name}...".format( |
||
90 | name=section.name)) |
||
91 | |||
92 | |||
93 | def nothing_done(log_printer): |
||
94 | """ |
||
95 | Will be called after processing a coafile when nothing had to be done, |
||
96 | i.e. no section was enabled/targeted. |
||
97 | |||
98 | :param log_printer: A LogPrinter object. |
||
99 | """ |
||
100 | log_printer.warn("No existent section was targeted or enabled. " |
||
101 | "Nothing to do.") |
||
102 | |||
103 | |||
104 | def acquire_actions_and_apply(console_printer, |
||
105 | log_printer, |
||
106 | section, |
||
107 | file_diff_dict, |
||
108 | result, |
||
109 | file_dict, |
||
110 | cli_actions=None): |
||
111 | """ |
||
112 | Acquires applicable actions and applies them. |
||
113 | |||
114 | :param console_printer: Object to print messages on the console. |
||
115 | :param log_printer: Printer responsible for logging the messages. |
||
116 | :param section: Name of section to which the result belongs. |
||
117 | :param file_diff_dict: Dictionary containing filenames as keys and Diff |
||
118 | objects as values. |
||
119 | :param result: A derivative of Result. |
||
120 | :param file_dict: A dictionary containing all files with filename as |
||
121 | key. |
||
122 | :param cli_actions: The list of cli actions available. |
||
123 | """ |
||
124 | cli_actions = CLI_ACTIONS if cli_actions is None else cli_actions |
||
125 | failed_actions = set() |
||
126 | while True: |
||
127 | actions = [] |
||
128 | for action in cli_actions: |
||
129 | if action.is_applicable(result, file_dict, file_diff_dict): |
||
130 | actions.append(action) |
||
131 | |||
132 | if actions == []: |
||
133 | return |
||
134 | |||
135 | action_dict = {} |
||
136 | metadata_list = [] |
||
137 | for action in actions: |
||
138 | metadata = action.get_metadata() |
||
139 | action_dict[metadata.name] = action |
||
140 | metadata_list.append(metadata) |
||
141 | |||
142 | # User can always choose no action which is guaranteed to succeed |
||
143 | if not ask_for_action_and_apply(log_printer, |
||
144 | console_printer, |
||
145 | section, |
||
146 | metadata_list, |
||
147 | action_dict, |
||
148 | failed_actions, |
||
149 | result, |
||
150 | file_diff_dict, |
||
151 | file_dict): |
||
152 | break |
||
153 | |||
154 | |||
155 | def print_lines(console_printer, |
||
156 | file_dict, |
||
157 | section, |
||
158 | sourcerange): |
||
159 | """ |
||
160 | Prints the lines between the current and the result line. If needed |
||
161 | they will be shortened. |
||
162 | |||
163 | :param console_printer: Object to print messages on the console. |
||
164 | :param file_dict: A dictionary containing all files as values with |
||
165 | filenames as key. |
||
166 | :param sourcerange: The SourceRange object referring to the related |
||
167 | lines to print. |
||
168 | """ |
||
169 | for i in range(sourcerange.start.line, sourcerange.end.line + 1): |
||
170 | # Print affected file's line number in the sidebar. |
||
171 | console_printer.print(format_lines(lines='', line_nr=i), |
||
172 | color=FILE_LINES_COLOR, |
||
173 | end='') |
||
174 | |||
175 | line = file_dict[sourcerange.file][i - 1].rstrip("\n") |
||
176 | try: |
||
177 | lexer = get_lexer_for_filename(sourcerange.file) |
||
178 | except ClassNotFound: |
||
179 | lexer = TextLexer() |
||
180 | lexer.add_filter(VisibleWhitespaceFilter( |
||
181 | spaces="•", tabs=True, |
||
182 | tabsize=SpacingHelper.DEFAULT_TAB_WIDTH)) |
||
183 | # highlight() combines lexer and formatter to output a ``str`` |
||
184 | # object. |
||
185 | printed_chars = 0 |
||
186 | if i == sourcerange.start.line and sourcerange.start.column: |
||
187 | console_printer.print(highlight_text( |
||
188 | line[:sourcerange.start.column-1], lexer), end='') |
||
189 | |||
190 | printed_chars = sourcerange.start.column-1 |
||
191 | |||
192 | if i == sourcerange.end.line and sourcerange.end.column: |
||
193 | console_printer.print(highlight_text( |
||
194 | line[printed_chars:sourcerange.end.column-1], |
||
195 | lexer, BackgroundSourceRangeStyle), end='') |
||
196 | |||
197 | console_printer.print(highlight_text( |
||
198 | line[sourcerange.end.column-1:], lexer), end='') |
||
199 | console_printer.print("") |
||
200 | |||
201 | else: |
||
202 | console_printer.print(highlight_text( |
||
203 | line[printed_chars:], lexer), end='') |
||
204 | console_printer.print("") |
||
205 | |||
206 | |||
207 | def print_result(console_printer, |
||
208 | log_printer, |
||
209 | section, |
||
210 | file_diff_dict, |
||
211 | result, |
||
212 | file_dict, |
||
213 | interactive=True): |
||
214 | """ |
||
215 | Prints the result to console. |
||
216 | |||
217 | :param console_printer: Object to print messages on the console. |
||
218 | :param log_printer: Printer responsible for logging the messages. |
||
219 | :param section: Name of section to which the result belongs. |
||
220 | :param file_diff_dict: Dictionary containing filenames as keys and Diff |
||
221 | objects as values. |
||
222 | :param result: A derivative of Result. |
||
223 | :param file_dict: A dictionary containing all files with filename as |
||
224 | key. |
||
225 | :interactive: Variable to check wether or not to |
||
226 | offer the user actions interactively. |
||
227 | """ |
||
228 | if not isinstance(result, Result): |
||
229 | log_printer.warn("One of the results can not be printed since it is " |
||
230 | "not a valid derivative of the coala result " |
||
231 | "class.") |
||
232 | return |
||
233 | |||
234 | console_printer.print(format_lines("[{sev}] {bear}:".format( |
||
235 | sev=RESULT_SEVERITY.__str__(result.severity), bear=result.origin)), |
||
236 | color=RESULT_SEVERITY_COLORS[result.severity]) |
||
237 | lexer = TextLexer() |
||
238 | result.message = highlight_text(result.message, lexer, |
||
239 | BackgroundMessageStyle) |
||
240 | console_printer.print(format_lines(result.message)) |
||
241 | |||
242 | if interactive: |
||
243 | cli_actions = CLI_ACTIONS |
||
244 | show_patch_action = ShowPatchAction() |
||
245 | if show_patch_action.is_applicable(result, file_dict, file_diff_dict): |
||
246 | diff_size = sum(len(diff) for diff in result.diffs.values()) |
||
247 | if diff_size <= DIFF_EXCERPT_MAX_SIZE: |
||
248 | show_patch_action.apply_from_section(result, |
||
249 | file_dict, |
||
250 | file_diff_dict, |
||
251 | section) |
||
252 | cli_actions = tuple(action for action in cli_actions |
||
253 | if not isinstance(action, ShowPatchAction)) |
||
254 | else: |
||
255 | print_diffs_info(result.diffs, console_printer) |
||
256 | acquire_actions_and_apply(console_printer, |
||
257 | log_printer, |
||
258 | section, |
||
259 | file_diff_dict, |
||
260 | result, |
||
261 | file_dict, |
||
262 | cli_actions) |
||
263 | |||
264 | |||
265 | def print_diffs_info(diffs, printer): |
||
266 | for filename, diff in sorted(diffs.items()): |
||
267 | additions, deletions = diff.stats() |
||
268 | printer.print( |
||
269 | format_lines("+{additions} -{deletions} in {file}".format( |
||
270 | file=filename, |
||
271 | additions=additions, |
||
272 | deletions=deletions)), |
||
273 | color='green') |
||
274 | |||
275 | |||
276 | def print_results_formatted(log_printer, |
||
277 | section, |
||
278 | result_list, |
||
279 | *args): |
||
280 | format_str = str(section.get( |
||
281 | "format_str", |
||
282 | "id:{id}:origin:{origin}:file:{file}:line:{line}:column:" |
||
283 | "{column}:end_line:{end_line}:end_column:{end_column}:severity:" |
||
284 | "{severity}:severity_str:{severity_str}:message:{message}")) |
||
285 | for result in result_list: |
||
286 | severity_str = RESULT_SEVERITY.__str__(result.severity) |
||
287 | try: |
||
288 | if len(result.affected_code) == 0: |
||
289 | print(format_str.format(file=None, |
||
290 | line=None, |
||
291 | end_line=None, |
||
292 | column=None, |
||
293 | end_column=None, |
||
294 | severity_str=severity_str, |
||
295 | **result.__dict__)) |
||
296 | continue |
||
297 | |||
298 | for range in result.affected_code: |
||
299 | print(format_str.format(file=range.start.file, |
||
300 | line=range.start.line, |
||
301 | end_line=range.end.line, |
||
302 | column=range.start.column, |
||
303 | end_column=range.end.column, |
||
304 | severity_str=severity_str, |
||
305 | **result.__dict__)) |
||
306 | except KeyError as exception: |
||
307 | log_printer.log_exception( |
||
308 | "Unable to print the result with the given format string.", |
||
309 | exception) |
||
310 | |||
311 | |||
312 | def print_affected_files(console_printer, |
||
313 | log_printer, |
||
314 | section, |
||
315 | result, |
||
316 | file_dict, |
||
317 | color=True): |
||
318 | """ |
||
319 | Print all the afected files and affected lines within them. |
||
320 | |||
321 | :param console_printer: Object to print messages on the console. |
||
322 | :param log_printer: Printer responsible for logging the messages. |
||
323 | :param section: The section to which the results belong to. |
||
324 | :param result_list: List containing the results |
||
325 | :param file_dict: A dictionary containing all files with filename as |
||
326 | key. |
||
327 | :param color: Boolean variable to print the results in color or |
||
328 | not. Can be used for testing. |
||
329 | """ |
||
330 | if len(result.affected_code) == 0: |
||
331 | console_printer.print("\n" + STR_PROJECT_WIDE, |
||
332 | color=FILE_NAME_COLOR) |
||
333 | else: |
||
334 | for sourcerange in result.affected_code: |
||
335 | if ( |
||
336 | sourcerange.file is not None and |
||
337 | sourcerange.file not in file_dict): |
||
338 | log_printer.warn("The context for the result ({}) cannot " |
||
339 | "be printed because it refers to a file " |
||
340 | "that doesn't seem to exist ({})" |
||
341 | ".".format(result, sourcerange.file)) |
||
342 | else: |
||
343 | print_affected_lines(console_printer, |
||
344 | file_dict, |
||
345 | section, |
||
346 | sourcerange) |
||
347 | |||
348 | |||
349 | def print_results_no_input(log_printer, |
||
350 | section, |
||
351 | result_list, |
||
352 | file_dict, |
||
353 | file_diff_dict, |
||
354 | color=True): |
||
355 | """ |
||
356 | Print all non interactive results in a section |
||
357 | |||
358 | :param log_printer: Printer responsible for logging the messages. |
||
359 | :param section: The section to which the results belong to. |
||
360 | :param result_list: List containing the results |
||
361 | :param file_dict: A dictionary containing all files with filename as |
||
362 | key. |
||
363 | :param file_diff_dict: A dictionary that contains filenames as keys and |
||
364 | diff objects as values. |
||
365 | :param color: Boolean variable to print the results in color or |
||
366 | not. Can be used for testing. |
||
367 | """ |
||
368 | console_printer = ConsolePrinter(print_colored=color) |
||
369 | for result in result_list: |
||
370 | |||
371 | print_affected_files(console_printer, |
||
372 | log_printer, |
||
373 | section, |
||
374 | result, |
||
375 | file_dict, |
||
376 | color=color) |
||
377 | |||
378 | print_result(console_printer, |
||
379 | log_printer, |
||
380 | section, |
||
381 | file_diff_dict, |
||
382 | result, |
||
383 | file_dict, |
||
384 | interactive=False) |
||
385 | |||
386 | |||
387 | def print_results(log_printer, |
||
388 | section, |
||
389 | result_list, |
||
390 | file_dict, |
||
391 | file_diff_dict, |
||
392 | color=True): |
||
393 | """ |
||
394 | Print all the results in a section. |
||
395 | |||
396 | :param log_printer: Printer responsible for logging the messages. |
||
397 | :param section: The section to which the results belong to. |
||
398 | :param result_list: List containing the results |
||
399 | :param file_dict: A dictionary containing all files with filename as |
||
400 | key. |
||
401 | :param file_diff_dict: A dictionary that contains filenames as keys and |
||
402 | diff objects as values. |
||
403 | :param color: Boolean variable to print the results in color or |
||
404 | not. Can be used for testing. |
||
405 | """ |
||
406 | console_printer = ConsolePrinter(print_colored=color) |
||
407 | |||
408 | for result in sorted(result_list): |
||
409 | |||
410 | print_affected_files(console_printer, |
||
411 | log_printer, |
||
412 | section, |
||
413 | result, |
||
414 | file_dict, |
||
415 | color=color) |
||
416 | |||
417 | print_result(console_printer, |
||
418 | log_printer, |
||
419 | section, |
||
420 | file_diff_dict, |
||
421 | result, |
||
422 | file_dict) |
||
423 | |||
424 | |||
425 | def print_affected_lines(console_printer, file_dict, section, sourcerange): |
||
426 | console_printer.print("\n" + os.path.relpath(sourcerange.file), |
||
427 | color=FILE_NAME_COLOR) |
||
428 | |||
429 | if sourcerange.start.line is not None: |
||
430 | if len(file_dict[sourcerange.file]) < sourcerange.end.line: |
||
431 | console_printer.print(format_lines(lines=STR_LINE_DOESNT_EXIST, |
||
432 | line_nr=sourcerange.end.line)) |
||
433 | else: |
||
434 | print_lines(console_printer, |
||
435 | file_dict, |
||
436 | section, |
||
437 | sourcerange) |
||
438 | |||
439 | |||
440 | def join_names(values): |
||
441 | """ |
||
442 | Produces a string by concatenating the items in ``values`` with |
||
443 | commas, except the last element, which is concatenated with an "and". |
||
444 | |||
445 | >>> join_names(["apples", "bananas", "oranges"]) |
||
446 | 'apples, bananas and oranges' |
||
447 | >>> join_names(["apples", "bananas"]) |
||
448 | 'apples and bananas' |
||
449 | >>> join_names(["apples"]) |
||
450 | 'apples' |
||
451 | |||
452 | :param values: |
||
453 | A list of strings. |
||
454 | :return: |
||
455 | The concatenated string. |
||
456 | """ |
||
457 | if len(values) > 1: |
||
458 | return ", ".join(values[:-1]) + " and " + values[-1] |
||
459 | else: |
||
460 | return values[0] |
||
461 | |||
462 | |||
463 | def require_setting(setting_name, arr): |
||
464 | """ |
||
465 | This method is responsible for prompting a user about a missing setting and |
||
466 | taking its value as input from the user. |
||
467 | |||
468 | :param setting_name: Name od the setting missing |
||
469 | :param arr: A list containing a description in [0] and the name |
||
470 | of the bears who need this setting in [1] and |
||
471 | following. |
||
472 | """ |
||
473 | needed = join_names(arr[1:]) |
||
474 | |||
475 | # Don't use input, it can't deal with escapes! |
||
476 | print(colored(STR_GET_VAL_FOR_SETTING.format(setting_name, arr[0], needed), |
||
477 | REQUIRED_SETTINGS_COLOR)) |
||
478 | return input() |
||
479 | |||
480 | |||
481 | def acquire_settings(log_printer, settings_names_dict): |
||
482 | """ |
||
483 | This method prompts the user for the given settings. |
||
484 | |||
485 | :param log_printer: |
||
486 | Printer responsible for logging the messages. This is needed to comply |
||
487 | with the interface. |
||
488 | :param settings_names_dict: |
||
489 | A dictionary with the settings name as key and a list containing a |
||
490 | description in [0] and the name of the bears who need this setting in |
||
491 | [1] and following. |
||
492 | |||
493 | Example: |
||
494 | |||
495 | :: |
||
496 | |||
497 | {"UseTabs": ["describes whether tabs should be used instead of spaces", |
||
498 | "SpaceConsistencyBear", |
||
499 | "SomeOtherBear"]} |
||
500 | |||
501 | :return: |
||
502 | A dictionary with the settings name as key and the given value as |
||
503 | value. |
||
504 | """ |
||
505 | if not isinstance(settings_names_dict, dict): |
||
506 | raise TypeError("The settings_names_dict parameter has to be a " |
||
507 | "dictionary.") |
||
508 | |||
509 | result = {} |
||
510 | for setting_name, arr in sorted(settings_names_dict.items(), |
||
511 | key=lambda x: (join_names(x[1][1:]), x[0])): |
||
512 | value = require_setting(setting_name, arr) |
||
513 | result.update({setting_name: value} if value is not None else {}) |
||
514 | |||
515 | return result |
||
516 | |||
517 | |||
518 | def get_action_info(section, action, failed_actions): |
||
519 | """ |
||
520 | Get all the required Settings for an action. It updates the section with |
||
521 | the Settings. |
||
522 | |||
523 | :param section: The section the action corresponds to. |
||
524 | :param action: The action to get the info for. |
||
525 | :param failed_actions: A set of all actions that have failed. A failed |
||
526 | action remains in the list until it is successfully |
||
527 | executed. |
||
528 | :return: Action name and the updated section. |
||
529 | """ |
||
530 | params = action.non_optional_params |
||
531 | |||
532 | for param_name in params: |
||
533 | if param_name not in section or action.name in failed_actions: |
||
534 | question = format_lines( |
||
535 | "Please enter a value for the parameter '{}' ({}): " |
||
536 | .format(param_name, params[param_name][0])) |
||
537 | section.append(Setting(param_name, input(question))) |
||
538 | |||
539 | return action.name, section |
||
540 | |||
541 | |||
542 | def choose_action(console_printer, actions): |
||
543 | """ |
||
544 | Presents the actions available to the user and takes as input the action |
||
545 | the user wants to choose. |
||
546 | |||
547 | :param console_printer: Object to print messages on the console. |
||
548 | :param actions: Actions available to the user. |
||
549 | :return: Return choice of action of user. |
||
550 | """ |
||
551 | console_printer.print(format_lines( |
||
552 | "The following actions are applicable to this result:")) |
||
553 | |||
554 | while True: |
||
555 | console_printer.print(format_lines("*0: " + |
||
556 | "Apply no further actions.")) |
||
557 | for i, action in enumerate(actions, 1): |
||
558 | console_printer.print(format_lines("{:>2}: {}".format( |
||
559 | i, |
||
560 | action.desc))) |
||
561 | |||
562 | try: |
||
563 | line = format_lines("Please enter the number of the action " |
||
564 | "you want to execute (Ctrl-D to exit). ") |
||
565 | |||
566 | choice = input(line) |
||
567 | if not choice: |
||
568 | return 0 |
||
569 | choice = int(choice) |
||
570 | if 0 <= choice <= len(actions): |
||
571 | return choice |
||
572 | except ValueError: |
||
573 | pass |
||
574 | |||
575 | console_printer.print(format_lines("Please enter a valid number.")) |
||
576 | |||
577 | |||
578 | def print_actions(console_printer, section, actions, failed_actions): |
||
579 | """ |
||
580 | Prints the given actions and lets the user choose. |
||
581 | |||
582 | :param console_printer: Object to print messages on the console. |
||
583 | :param actions: A list of FunctionMetadata objects. |
||
584 | :param failed_actions: A set of all actions that have failed. A failed |
||
585 | action remains in the list until it is |
||
586 | successfully executed. |
||
587 | :return: A tuple with the name member of the |
||
588 | FunctionMetadata object chosen by the user |
||
589 | and a Section containing at least all needed |
||
590 | values for the action. If the user did |
||
591 | choose to do nothing, return (None, None). |
||
592 | """ |
||
593 | choice = choose_action(console_printer, actions) |
||
594 | |||
595 | if choice == 0: |
||
596 | return None, None |
||
597 | |||
598 | return get_action_info(section, actions[choice - 1], failed_actions) |
||
599 | |||
600 | |||
601 | def ask_for_action_and_apply(log_printer, |
||
602 | console_printer, |
||
603 | section, |
||
604 | metadata_list, |
||
605 | action_dict, |
||
606 | failed_actions, |
||
607 | result, |
||
608 | file_diff_dict, |
||
609 | file_dict): |
||
610 | """ |
||
611 | Asks the user for an action and applies it. |
||
612 | |||
613 | :param log_printer: Printer responsible for logging the messages. |
||
614 | :param console_printer: Object to print messages on the console. |
||
615 | :param section: Currently active section. |
||
616 | :param metadata_list: Contains metadata for all the actions. |
||
617 | :param action_dict: Contains the action names as keys and their |
||
618 | references as values. |
||
619 | :param failed_actions: A set of all actions that have failed. A failed |
||
620 | action remains in the list until it is successfully |
||
621 | executed. |
||
622 | :param result: Result corresponding to the actions. |
||
623 | :param file_diff_dict: If it is an action which applies a patch, this |
||
624 | contains the diff of the patch to be applied to |
||
625 | the file with filename as keys. |
||
626 | :param file_dict: Dictionary with filename as keys and its contents |
||
627 | as values. |
||
628 | :return: Returns a boolean value. True will be returned, if |
||
629 | it makes sense that the user may choose to execute |
||
630 | another action, False otherwise. |
||
631 | """ |
||
632 | action_name, section = print_actions(console_printer, section, |
||
633 | metadata_list, failed_actions) |
||
634 | if action_name is None: |
||
635 | return False |
||
636 | |||
637 | chosen_action = action_dict[action_name] |
||
638 | try: |
||
639 | chosen_action.apply_from_section(result, |
||
640 | file_dict, |
||
641 | file_diff_dict, |
||
642 | section) |
||
643 | console_printer.print( |
||
644 | format_lines(chosen_action.SUCCESS_MESSAGE), |
||
645 | color=SUCCESS_COLOR) |
||
646 | failed_actions.discard(action_name) |
||
647 | except Exception as exception: # pylint: disable=broad-except |
||
648 | log_printer.log_exception("Failed to execute the action " |
||
649 | "{} with error: {}.".format(action_name, |
||
650 | exception), |
||
651 | exception) |
||
652 | failed_actions.add(action_name) |
||
653 | return True |
||
654 | |||
655 | |||
656 | def show_enumeration(console_printer, |
||
657 | title, |
||
658 | items, |
||
659 | indentation, |
||
660 | no_items_text): |
||
661 | """ |
||
662 | This function takes as input an iterable object (preferably a list or |
||
663 | a dict). And prints in a stylized format. If the iterable object is |
||
664 | empty, it prints a specific statement given by the user. An e.g : |
||
665 | |||
666 | <indentation>Title: |
||
667 | <indentation> * Item 1 |
||
668 | <indentation> * Item 2 |
||
669 | |||
670 | :param console_printer: Object to print messages on the console. |
||
671 | :param title: Title of the text to be printed |
||
672 | :param items: The iterable object. |
||
673 | :param indentation: Number of spaces to indent every line by. |
||
674 | :param no_items_text: Text printed when iterable object is empty. |
||
675 | """ |
||
676 | if not items: |
||
677 | console_printer.print(indentation + no_items_text) |
||
678 | else: |
||
679 | console_printer.print(indentation + title) |
||
680 | if isinstance(items, dict): |
||
681 | for key, value in items.items(): |
||
682 | console_printer.print(indentation + " * " + key + ": " + |
||
683 | value[0]) |
||
684 | else: |
||
685 | for item in items: |
||
686 | console_printer.print(indentation + " * " + item) |
||
687 | console_printer.print() |
||
688 | |||
689 | |||
690 | def show_bear(bear, |
||
691 | show_description, |
||
692 | show_params, |
||
693 | console_printer): |
||
694 | """ |
||
695 | Display all information about a bear. |
||
696 | |||
697 | :param bear: The bear to be displayed. |
||
698 | :param show_description: True if the main description should be shown. |
||
699 | :param show_params: True if the details should be shown. |
||
700 | :param console_printer: Object to print messages on the console. |
||
701 | """ |
||
702 | console_printer.print(bear.name, color="blue") |
||
703 | |||
704 | if not show_description and not show_params: |
||
705 | return |
||
706 | |||
707 | metadata = bear.get_metadata() |
||
708 | |||
709 | if show_description: |
||
710 | console_printer.print( |
||
711 | " " + metadata.desc.replace("\n", "\n ")) |
||
712 | console_printer.print() # Add a newline |
||
713 | |||
714 | if show_params: |
||
715 | show_enumeration( |
||
716 | console_printer, "Supported languages:", |
||
717 | bear.LANGUAGES, |
||
718 | " ", |
||
719 | "The bear does not provide information about which languages " |
||
720 | "it can analyze.") |
||
721 | show_enumeration(console_printer, |
||
722 | "Needed Settings:", |
||
723 | metadata.non_optional_params, |
||
724 | " ", |
||
725 | "No needed settings.") |
||
726 | show_enumeration(console_printer, |
||
727 | "Optional Settings:", |
||
728 | metadata.optional_params, |
||
729 | " ", |
||
730 | "No optional settings.") |
||
731 | show_enumeration(console_printer, |
||
732 | "Can detect:", |
||
733 | bear.can_detect, |
||
734 | " ", |
||
735 | "This bear does not provide information about what " |
||
736 | "categories it can detect.") |
||
737 | show_enumeration(console_printer, |
||
738 | "Can fix:", |
||
739 | bear.CAN_FIX, |
||
740 | " ", |
||
741 | "This bear cannot fix issues or does not provide " |
||
742 | "information about what categories it can fix.") |
||
743 | |||
744 | |||
745 | def print_bears(bears, |
||
746 | show_description, |
||
747 | show_params, |
||
748 | console_printer): |
||
749 | """ |
||
750 | Presents all bears being used in a stylized manner. |
||
751 | |||
752 | :param bears: It's a dictionary with bears as keys and list of |
||
753 | sections containing those bears as values. |
||
754 | :param show_description: True if the main description of the bears should |
||
755 | be shown. |
||
756 | :param show_params: True if the parameters and their description |
||
757 | should be shown. |
||
758 | :param console_printer: Object to print messages on the console. |
||
759 | """ |
||
760 | if not bears: |
||
761 | console_printer.print("No bears to show. Did you forget to install " |
||
762 | "the `coala-bears` package? Try `pip3 install " |
||
763 | "coala-bears`.") |
||
764 | return |
||
765 | |||
766 | for bear, sections in sorted(bears.items(), |
||
767 | key=lambda bear_tuple: bear_tuple[0].name): |
||
768 | show_bear(bear, |
||
769 | show_description, |
||
770 | show_params, |
||
771 | console_printer) |
||
772 | |||
773 | |||
774 | def show_bears(local_bears, |
||
775 | global_bears, |
||
776 | show_description, |
||
777 | show_params, |
||
778 | console_printer): |
||
779 | """ |
||
780 | Extracts all the bears from each enabled section or the sections in the |
||
781 | targets and passes a dictionary to the show_bears_callback method. |
||
782 | |||
783 | :param local_bears: Dictionary of local bears with section names |
||
784 | as keys and bear list as values. |
||
785 | :param global_bears: Dictionary of global bears with section |
||
786 | names as keys and bear list as values. |
||
787 | :param show_description: True if the main description of the bears should |
||
788 | be shown. |
||
789 | :param show_params: True if the parameters and their description |
||
790 | should be shown. |
||
791 | :param console_printer: Object to print messages on the console. |
||
792 | """ |
||
793 | bears = inverse_dicts(local_bears, global_bears) |
||
794 | |||
795 | print_bears(bears, show_description, show_params, console_printer) |
||
796 | |||
797 | |||
798 | def show_language_bears_capabilities(language_bears_capabilities, |
||
799 | console_printer): |
||
800 | """ |
||
801 | Display what the bears can detect and fix. |
||
802 | |||
803 | :param language_bears_capabilities: |
||
804 | Dictionary with languages as keys and their bears' capabilities as |
||
805 | values. The capabilities are stored in a tuple of two elements where the |
||
806 | first one represents what the bears can detect, and the second one what |
||
807 | they can fix. |
||
808 | :param console_printer: |
||
809 | Object to print messages on the console. |
||
810 | """ |
||
811 | if not language_bears_capabilities: |
||
812 | console_printer.print("There is no bear available for this language") |
||
813 | else: |
||
814 | for language, capabilities in language_bears_capabilities.items(): |
||
815 | if capabilities[0]: |
||
816 | console_printer.print('coala can do the following for ', end='') |
||
817 | console_printer.print(language.upper(), color="blue") |
||
818 | console_printer.print(" Can detect only: ", end='') |
||
819 | console_printer.print( |
||
820 | ', '.join(sorted(capabilities[0])), color=CAPABILITY_COLOR) |
||
821 | if capabilities[1]: |
||
822 | console_printer.print(" Can fix : ", end='') |
||
823 | console_printer.print( |
||
824 | ', '.join(sorted(capabilities[1])), |
||
825 | color=CAPABILITY_COLOR) |
||
826 | else: |
||
827 | console_printer.print('coala does not support ', color='red', |
||
828 | end='') |
||
829 | console_printer.print(language, color='blue') |
||
830 |