Source code for coalib.output.ConsoleInteraction

import logging

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
import os.path

from coalib.misc.DictUtilities import inverse_dicts
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.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 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, lexer=TextLexer(), style=None): if style: formatter = TerminalTrueColorFormatter(style=style) else: formatter = TerminalTrueColorFormatter() 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:' 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()) DIFF_EXCERPT_MAX_SIZE = 4
[docs]def format_lines(lines, line_nr=''): return '\n'.join('|{:>4}| {}'.format(line_nr, line) for line in lines.rstrip('\n').split('\n'))
[docs]def nothing_done(log_printer): """ 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. """ log_printer.warn('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): """ 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 cli_actions: The list of cli actions available. """ cli_actions = CLI_ACTIONS if cli_actions is None else cli_actions failed_actions = set() while True: actions = [] for action in cli_actions: if action.is_applicable(result, file_dict, file_diff_dict): actions.append(action) if actions == []: return action_dict = {} metadata_list = [] for action in actions: metadata = action.get_metadata() action_dict[metadata.name] = action metadata_list.append(metadata) # User can always choose no action which is guaranteed to succeed if not ask_for_action_and_apply(console_printer, section, metadata_list, action_dict, failed_actions, result, file_diff_dict, file_dict): break
_warn_deprecated_format_str = True # Remove when format_str is deprecated
[docs]def join_names(values): """ Produces a string by concatenating the items in ``values`` with commas, except the last element, which is concatenated with an "and". >>> join_names(["apples", "bananas", "oranges"]) 'apples, bananas and oranges' >>> join_names(["apples", "bananas"]) 'apples and bananas' >>> join_names(["apples"]) 'apples' :param values: A list of strings. :return: The concatenated string. """ if len(values) > 1: return ', '.join(values[:-1]) + ' and ' + values[-1] else: return values[0]
[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. """ 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])) section.append(Setting(param_name, input(question))) return action.name, section
[docs]def choose_action(console_printer, actions): """ 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. :return: Return choice of action of user. """ while True: console_printer.print(format_lines('*0: ' + 'Do nothing')) for i, action in enumerate(actions, 1): console_printer.print(format_lines('{:>2}: {}'.format( i, action.desc))) try: line = format_lines('Enter number (Ctrl-D to exit): ') choice = input(line) if not choice: return 0 choice = int(choice) if 0 <= choice <= len(actions): return choice except ValueError: pass console_printer.print(format_lines('Please enter a valid number.'))
[docs]def ask_for_action_and_apply(console_printer, section, metadata_list, action_dict, failed_actions, result, file_diff_dict, file_dict): """ 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. :return: Returns a boolean value. True will be returned, if it makes sense that the user may choose to execute another action, False otherwise. """ action_name, section = print_actions(console_printer, section, metadata_list, failed_actions) if action_name is None: return False chosen_action = action_dict[action_name] try: chosen_action.apply_from_section(result, file_dict, file_diff_dict, section) console_printer.print( format_lines(chosen_action.SUCCESS_MESSAGE), color=SUCCESS_COLOR) 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) 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): """ Displays all information about a bear. :param bear: The bear to be displayed. :param show_description: True if the main description should be shown. :param show_params: True if the details should be shown. :param console_printer: Object to print messages on the console. """ console_printer.print(bear.name, color='blue') if not show_description and not show_params: return metadata = bear.get_metadata() 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.') 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.') 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.')
[docs]def show_bears(local_bears, global_bears, show_description, show_params, console_printer): """ 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: True if the main description of the bears should be shown. :param show_params: True if the parameters and their description should be shown. :param console_printer: Object to print messages on the console. """ bears = inverse_dicts(local_bears, global_bears) print_bears(bears, show_description, show_params, console_printer)
[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')