Issues (70)

coalib/output/ConsoleInteraction.py (1 issue)

Severity
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 TerminalFormatter imported from pygments.formatters
Loading history...
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