Source code for coalib.output.ConsoleInteraction

import copy
import logging
import platform
import os
from collections import OrderedDict

from termcolor import colored

try:
    # This import has side effects and is needed to make input() behave nicely
    import readline  # pylint: disable=unused-import
except ImportError:  # pragma: no cover
    pass

from coalib.misc.DictUtilities import inverse_dicts
from coalib.misc.Exceptions import log_exception
from coalib.misc.DeprecationUtilities import check_deprecation
from coalib.bearlib.spacing.SpacingHelper import SpacingHelper
from coalib.results.Result import Result
from coalib.results.result_actions.ApplyPatchAction import ApplyPatchAction
from coalib.results.result_actions.OpenEditorAction import OpenEditorAction
from coalib.results.result_actions.IgnoreResultAction import IgnoreResultAction
from coalib.results.result_actions.DoNothingAction import DoNothingAction
from coalib.results.result_actions.GeneratePatchesAction import (
    GeneratePatchesAction)
from coalib.results.result_actions.ShowAppliedPatchesAction import (
    ShowAppliedPatchesAction)
from coalib.results.result_actions.PrintDebugMessageAction import (
    PrintDebugMessageAction)
from coalib.results.result_actions.PrintMoreInfoAction import (
    PrintMoreInfoAction)
from coalib.results.result_actions.ShowPatchAction import ShowPatchAction
from coalib.results.RESULT_SEVERITY import (
    RESULT_SEVERITY, RESULT_SEVERITY_COLORS)
from coalib.settings.Setting import Setting
from coala_utils.string_processing.Core import join_names

from pygments import highlight
from pygments.formatters import (TerminalTrueColorFormatter,
                                 TerminalFormatter)
from pygments.filters import VisibleWhitespaceFilter
from pygments.lexers import TextLexer, get_lexer_for_filename
from pygments.style import Style
from pygments.token import Token
from pygments.util import ClassNotFound


[docs]class BackgroundSourceRangeStyle(Style): styles = { Token: 'bold bg:#BB4D3E #111' }
[docs]class BackgroundMessageStyle(Style): styles = { Token: 'bold bg:#eee #111' }
[docs]class NoColorStyle(Style): styles = { Token: 'noinherit' }
[docs]def highlight_text(no_color, text, style, lexer=TextLexer()): formatter = TerminalTrueColorFormatter(style=style) if no_color: formatter = TerminalTrueColorFormatter(style=NoColorStyle) return highlight(text, lexer, formatter)[:-1]
STR_GET_VAL_FOR_SETTING = ('Please enter a value for the setting \"{}\" ({}) ' 'needed by {} for section \"{}\": ') STR_LINE_DOESNT_EXIST = ('The line belonging to the following result ' 'cannot be printed because it refers to a line ' "that doesn't seem to exist in the given file.") STR_PROJECT_WIDE = 'Project wide:' STR_ENTER_NUMBER = 'Enter number (Ctrl-{} to exit): '.format( 'Z' if platform.system() == 'Windows' else 'D') FILE_NAME_COLOR = 'blue' FILE_LINES_COLOR = 'blue' CAPABILITY_COLOR = 'green' HIGHLIGHTED_CODE_COLOR = 'red' SUCCESS_COLOR = 'green' REQUIRED_SETTINGS_COLOR = 'green' CLI_ACTIONS = (OpenEditorAction(), ApplyPatchAction(), PrintDebugMessageAction(), PrintMoreInfoAction(), ShowPatchAction(), IgnoreResultAction(), ShowAppliedPatchesAction(), GeneratePatchesAction()) DIFF_EXCERPT_MAX_SIZE = 4
[docs]def color_letter(console_printer, line): x = -1 y = -1 letter = '' for i, l in enumerate(line, 0): if line[i] == '(': x = i if line[i] == ')': y = i if l.isupper() and x != -1: letter = l first_part = line[:x+1] second_part = line[y:] console_printer.print(first_part, end='') console_printer.print(letter, color='blue', end='') console_printer.print(second_part)
[docs]def format_lines(lines, symbol='', line_nr=''): def sym(x): return ']' if x is '[' else x return '\n'.join('{}{:>4}{} {}'.format(symbol, line_nr, sym(symbol), line) for line in lines.rstrip('\n').split('\n'))
[docs]def nothing_done(log_printer=None): """ Will be called after processing a coafile when nothing had to be done, i.e. no section was enabled/targeted. :param log_printer: A LogPrinter object. """ logging.warning('No existent section was targeted or enabled. Nothing to ' 'do.')
[docs]def acquire_actions_and_apply(console_printer, section, file_diff_dict, result, file_dict, cli_actions=None, apply_single=False): """ Acquires applicable actions and applies them. :param console_printer: Object to print messages on the console. :param section: Name of section to which the result belongs. :param file_diff_dict: Dictionary containing filenames as keys and Diff objects as values. :param result: A derivative of Result. :param file_dict: A dictionary containing all files with filename as key. :param apply_single: The action that should be applied for all results. If it's not selected, has a value of False. :param cli_actions: The list of cli actions available. """ cli_actions = CLI_ACTIONS if cli_actions is None else cli_actions failed_actions = set() applied_actions = {} while True: action_dict = {} metadata_list = [] for action in cli_actions: if action.is_applicable(result, file_dict, file_diff_dict, tuple(applied_actions.keys())) is True: metadata = action.get_metadata() action_dict[metadata.name] = action metadata_list.append(metadata) if not metadata_list: return # User can always choose no action which is guaranteed to succeed continue_interaction = ask_for_action_and_apply( console_printer, section, metadata_list, action_dict, failed_actions, result, file_diff_dict, file_dict, applied_actions, apply_single=apply_single ) if not continue_interaction: break
[docs]def require_setting(setting_name, arr, section): """ This method is responsible for prompting a user about a missing setting and taking its value as input from the user. :param setting_name: Name of the setting missing :param arr: A list containing a description in [0] and the name of the bears who need this setting in [1] and following. :param section: The section the action corresponds to. :param return: Returns the setting value that was requested from the user. """ needed = join_names(arr[1:]) # Don't use input, it can't deal with escapes! print(colored(STR_GET_VAL_FOR_SETTING.format(setting_name, arr[0], needed, section.name), REQUIRED_SETTINGS_COLOR)) return input()
[docs]def acquire_settings(log_printer, settings_names_dict, section): """ This method prompts the user for the given settings. :param log_printer: Printer responsible for logging the messages. This is needed to comply with the interface. :param settings_names_dict: A dictionary with the settings name as key and a list containing a description in [0] and the name of the bears who need this setting in [1] and following. Example: :: {"UseTabs": ["describes whether tabs should be used instead of spaces", "SpaceConsistencyBear", "SomeOtherBear"]} :param section: The section the action corresponds to. :return: A dictionary with the settings name as key and the given value as value. """ if not isinstance(settings_names_dict, dict): raise TypeError('The settings_names_dict parameter has to be a ' 'dictionary.') result = {} for setting_name, arr in sorted(settings_names_dict.items(), key=lambda x: (join_names(x[1][1:]), x[0])): value = require_setting(setting_name, arr, section) result.update({setting_name: value} if value is not None else {}) return result
[docs]def get_action_info(section, action, failed_actions): """ Gets all the required Settings for an action. It updates the section with the Settings. :param section: The section the action corresponds to. :param action: The action to get the info for. :param failed_actions: A set of all actions that have failed. A failed action remains in the list until it is successfully executed. :return: Action name and the updated section. """ params = action.non_optional_params for param_name in params: if param_name not in section or action.name in failed_actions: question = format_lines( "Please enter a value for the parameter '{}' ({}): " .format(param_name, params[param_name][0]), symbol='!') section.append(Setting(param_name, input(question))) return action.name, section
[docs]def choose_action(console_printer, actions, apply_single=False): """ Presents the actions available to the user and takes as input the action the user wants to choose. :param console_printer: Object to print messages on the console. :param actions: Actions available to the user. :param apply_single: The action that should be applied for all results. If it's not selected, has a value of False. :return: Return a tuple of lists, a list with the names of actions that needs to be applied and a list with with the description of the actions. """ actions_desc = [] actions_name = [] if apply_single: for i, action in enumerate(actions, 0): if apply_single == action.desc: return ([action.desc], [action.name]) return (['Do (N)othing'], ['Do (N)othing']) else: while True: for i, action in enumerate(actions, 0): output = '{:>2}. {}' if i != 0 else '*{}. {}' color_letter(console_printer, format_lines(output.format( i, action.desc), symbol='[')) line = format_lines(STR_ENTER_NUMBER, symbol='[') choice = input(line) choice = str(choice) for c in choice: c = str(c) actions_desc_len = len(actions_desc) if c.isnumeric(): for i, action in enumerate(actions, 0): c = int(c) if i == c: actions_desc.append(action.desc) actions_name.append(action.name) break elif c.isalpha(): c = c.upper() c = '(' + c + ')' for i, action in enumerate(actions, 1): if c in action.desc: actions_desc.append(action.desc) actions_name.append(action.name) break if actions_desc_len == len(actions_desc): console_printer.print(format_lines( 'Please enter a valid letter or number.', symbol='[')) if not choice: actions_desc.append(DoNothingAction().get_metadata().desc) actions_name.append(DoNothingAction().get_metadata().name) return (actions_desc, actions_name)
[docs]def try_to_apply_action(action_name, chosen_action, console_printer, section, metadata_list, action_dict, failed_actions, result, file_diff_dict, file_dict, applied_actions): """ Try to apply the given action. :param action_name: The name of the action. :param chosen_action: The action object that will be applied. :param console_printer: Object to print messages on the console. :param section: Currently active section. :param metadata_list: Contains metadata for all the actions. :param action_dict: Contains the action names as keys and their references as values. :param failed_actions: A set of all actions that have failed. A failed action remains in the list until it is successfully executed. :param result: Result corresponding to the actions. :param file_diff_dict: If it is an action which applies a patch, this contains the diff of the patch to be applied to the file with filename as keys. :param applied_actions: A dictionary that contains the result, file_dict, file_diff_dict and the section for an action. :param file_dict: Dictionary with filename as keys and its contents as values. """ try: chosen_action.apply_from_section(result, file_dict, file_diff_dict, section) if not isinstance(chosen_action, DoNothingAction): console_printer.print( format_lines(chosen_action.SUCCESS_MESSAGE, symbol='['), color=SUCCESS_COLOR) applied_actions[action_name] = [copy.copy(result), copy.copy( file_dict), copy.copy(file_diff_dict), copy.copy(section)] result.set_applied_actions(applied_actions) failed_actions.discard(action_name) except Exception as exception: # pylint: disable=broad-except logging.error('Failed to execute the action {} with error: {}.' .format(action_name, exception)) failed_actions.add(action_name)
[docs]def ask_for_action_and_apply(console_printer, section, metadata_list, action_dict, failed_actions, result, file_diff_dict, file_dict, applied_actions, apply_single=False): """ Asks the user for an action and applies it. :param console_printer: Object to print messages on the console. :param section: Currently active section. :param metadata_list: Contains metadata for all the actions. :param action_dict: Contains the action names as keys and their references as values. :param failed_actions: A set of all actions that have failed. A failed action remains in the list until it is successfully executed. :param result: Result corresponding to the actions. :param file_diff_dict: If it is an action which applies a patch, this contains the diff of the patch to be applied to the file with filename as keys. :param file_dict: Dictionary with filename as keys and its contents as values. :param apply_single: The action that should be applied for all results. If it's not selected, has a value of False. :param applied_actions: A dictionary that contains the result, file_dict, file_diff_dict and the section for an action. :return: Returns a boolean value. True will be returned, if it makes sense that the user may choose to execute another action, False otherwise. If apply_single isn't set, always return False. """ do_nothing_action = DoNothingAction() metadata_list.insert(0, do_nothing_action.get_metadata()) action_dict[do_nothing_action.get_metadata().name] = DoNothingAction() actions_desc, actions_name = choose_action(console_printer, metadata_list, apply_single) if apply_single: for index, action_details in enumerate(metadata_list, 1): if apply_single == action_details.desc: action_name, section = get_action_info( section, metadata_list[index - 1], failed_actions) chosen_action = action_dict[action_details.name] try_to_apply_action(action_name, chosen_action, console_printer, section, metadata_list, action_dict, failed_actions, result, file_diff_dict, file_dict, applied_actions) return False else: for action_choice, action_choice_name in zip(actions_desc, actions_name): chosen_action = action_dict[action_choice_name] action_choice_made = action_choice for index, action_details in enumerate(metadata_list, 1): if action_choice_made in action_details.desc: action_name, section = get_action_info( section, metadata_list[index-1], failed_actions) try_to_apply_action(action_name, chosen_action, console_printer, section, metadata_list, action_dict, failed_actions, result, file_diff_dict, file_dict, applied_actions) if action_choice == 'Do (N)othing': return False return True
[docs]def show_enumeration(console_printer, title, items, indentation, no_items_text): """ This function takes as input an iterable object (preferably a list or a dict) and prints it in a stylized format. If the iterable object is empty, it prints a specific statement given by the user. An e.g : <indentation>Title: <indentation> * Item 1 <indentation> * Item 2 :param console_printer: Object to print messages on the console. :param title: Title of the text to be printed :param items: The iterable object. :param indentation: Number of spaces to indent every line by. :param no_items_text: Text printed when iterable object is empty. """ if not items: console_printer.print(indentation + no_items_text) else: console_printer.print(indentation + title) if isinstance(items, dict): for key, value in items.items(): console_printer.print(indentation + ' * ' + key + ': ' + value[0]) else: for item in items: console_printer.print(indentation + ' * ' + item) console_printer.print()
[docs]def show_bear(bear, show_description, show_params, console_printer, args=None): """ Displays all information about a bear. :param bear: The bear to be displayed. :param show_description: This parameter is deprecated. :param show_params: This parameter is deprecated. :param console_printer: Object to print messages on the console. :param args: Args passed to coala command. """ console_printer.print(bear.name, color='blue') metadata = bear.get_metadata() check_deprecation(OrderedDict([ ('show_description', show_description), ('show_params', show_params)])) if show_description: console_printer.print( ' ' + metadata.desc.replace('\n', '\n ')) console_printer.print() # Add a newline if show_params: show_enumeration( console_printer, 'Supported languages:', bear.LANGUAGES, ' ', 'The bear does not provide information about which languages ' 'it can analyze.') if (args and args.show_settings) or show_params: show_enumeration(console_printer, 'Needed Settings:', metadata.non_optional_params, ' ', 'No needed settings.') show_enumeration(console_printer, 'Optional Settings:', metadata.optional_params, ' ', 'No optional settings.') if show_params: show_enumeration(console_printer, 'Can detect:', bear.can_detect, ' ', 'This bear does not provide information about what ' 'categories it can detect.') show_enumeration(console_printer, 'Can fix:', bear.CAN_FIX, ' ', 'This bear cannot fix issues or does not provide ' 'information about what categories it can fix.') console_printer.print( ' Path:\n' + ' ' + repr(bear.source_location) + '\n')
[docs]def show_bears(local_bears, global_bears, show_description, show_params, console_printer, args=None): """ Extracts all the bears from each enabled section or the sections in the targets and passes a dictionary to the show_bears_callback method. :param local_bears: Dictionary of local bears with section names as keys and bear list as values. :param global_bears: Dictionary of global bears with section names as keys and bear list as values. :param show_description: This parameter is deprecated. :param show_params: This parameter is deprecated. :param console_printer: Object to print messages on the console. :param args: Args passed to coala command. """ bears = inverse_dicts(local_bears, global_bears) print_bears(bears, show_description, show_params, console_printer, args)
[docs]def show_language_bears_capabilities(language_bears_capabilities, console_printer): """ Displays what the bears can detect and fix. :param language_bears_capabilities: Dictionary with languages as keys and their bears' capabilities as values. The capabilities are stored in a tuple of two elements where the first one represents what the bears can detect, and the second one what they can fix. :param console_printer: Object to print messages on the console. """ if not language_bears_capabilities: console_printer.print('There is no bear available for this language') else: for language, capabilities in language_bears_capabilities.items(): if capabilities[0]: console_printer.print( 'coala can do the following for ', end='') console_printer.print(language.upper(), color='blue') console_printer.print(' Can detect only: ', end='') console_printer.print( ', '.join(sorted(capabilities[0])), color=CAPABILITY_COLOR) if capabilities[1]: console_printer.print(' Can fix : ', end='') console_printer.print( ', '.join(sorted(capabilities[1])), color=CAPABILITY_COLOR) else: console_printer.print('coala does not support ', color='red', end='') console_printer.print(language, color='blue')