From 88f6996d69042ee79eefd804c7ca437d54176439 Mon Sep 17 00:00:00 2001 From: satwikkansal Date: Fri, 14 Jul 2017 14:52:16 +0530 Subject: [PATCH] Bears.py: Add bears based on capabilties Modifies existing `filter_relevant_bears` methods to also include bears based on `DEFAULT_CAPABILITIES` defined in "Constants.py". Only the bears with unique capabilities are added to final list of selected bears and perference is given to "FIX" bears and the bears which have their prerequisites already set up. Related to https://github.com/coala/coala-quickstart/issues/131 --- coala_quickstart/Constants.py | 11 ++ coala_quickstart/generation/Bears.py | 166 ++++++++++++++++++++++++++- tests/generation/Bears.py | 1 - 3 files changed, 173 insertions(+), 5 deletions(-) diff --git a/coala_quickstart/Constants.py b/coala_quickstart/Constants.py index 2fd9db5..83d0d51 100644 --- a/coala_quickstart/Constants.py +++ b/coala_quickstart/Constants.py @@ -9,3 +9,14 @@ "JavaScript": {"JSHintBear", "JSComplexityBear"}, "Java": {"JavaPMD", "CheckstyleBear"}, "Python": {"PycodestyleBear"}} + +DEFAULT_CAPABILTIES = { + "Syntax", + "Formatting", + "Documentation", + "Redundancy", + "Spelling", + "Smell", + "Code Simplification", + "Complexity", +} diff --git a/coala_quickstart/generation/Bears.py b/coala_quickstart/generation/Bears.py index 5025b94..952d7ff 100644 --- a/coala_quickstart/generation/Bears.py +++ b/coala_quickstart/generation/Bears.py @@ -1,9 +1,11 @@ import copy +import random import re +from collections import defaultdict from pyprint.NullPrinter import NullPrinter -from coala_quickstart.Constants import IMPORTANT_BEAR_LIST +from coala_quickstart.Constants import IMPORTANT_BEAR_LIST, DEFAULT_CAPABILTIES from coala_quickstart.Strings import BEAR_HELP from coala_quickstart.generation.SettingsFilling import is_autofill_possible from coalib.settings.ConfigurationGathering import get_filtered_bears @@ -62,13 +64,22 @@ def filter_relevant_bears(used_languages, if bear.__name__ in IMPORTANT_BEAR_LIST[lang]: selected_bears[lang].add(bear) if lang_bears and lang not in IMPORTANT_BEAR_LIST: - selected_bears[lang] = lang_bears + selected_bears[lang] = set(lang_bears) candidate_bears[lang] = set( [bear for bear in lang_bears if lang in selected_bears and bear not in selected_bears[lang]]) + # Filter bears based on default capabilties + capable_candidates = {} + desired_capabilities = DEFAULT_CAPABILTIES + for lang, lang_bears in candidate_bears.items(): + # Eliminate bears which doesn't contain the desired capabilites + capable_bears = get_bears_with_given_capabilities( + lang_bears, desired_capabilities) + capable_candidates[lang] = capable_bears + project_dependency_info = extracted_info.get("ProjectDependencyInfo") # Use project_dependency_info to propose bears to user. @@ -102,6 +113,29 @@ def filter_relevant_bears(used_languages, # no non-optional setting, select it right away! selected_bears[lang].add(bear) + # capabilities satisfied till now + satisfied_capabilities = get_bears_capabilties(selected_bears) + remaining_capabilities = { + lang: [cap for cap in desired_capabilities + if satisfied_capabilities.get(lang) and + cap not in satisfied_capabilities[lang]] + for lang in candidate_bears} + + filtered_bears = {} + for lang, lang_bears in capable_candidates.items(): + filtered_bears[lang] = get_bears_with_given_capabilities( + lang_bears, remaining_capabilities[lang]) + + # Remove overlapping capabilty bears + filtered_bears = remove_bears_with_conflicting_capabilties( + filtered_bears) + # Add to the selectecd_bears + for lang, lang_bears in filtered_bears.items(): + if not selected_bears.get(lang): + selected_bears[lang] = lang_bears + else: + selected_bears[lang].update(lang_bears) + return selected_bears @@ -238,19 +272,140 @@ def get_bears_with_matching_dependencies(bears, dependency_info): # matched_requirements. matched_requirements.append(req) - result = [] + result = set() for bear in bears: all_req_satisfied = True for req in bear.REQUIREMENTS: if req.package not in matched_requirements: all_req_satisfied = False if bear.REQUIREMENTS and all_req_satisfied: - result.append(bear) + result.add(bear) + return result + + +def get_bears_with_given_capabilities(bears, capabilities): + """ + Returns a list of bears which contain at least one on the + capability in ``capabilities`` list. + + :param bears: list of bears. + :param capabilities: A list of bear capabilities that coala + supports + """ + result = set() + for bear in bears: + can_detect_caps = [c for c in list(bear.CAN_DETECT)] + can_fix_caps = [c for c in list(bear.CAN_FIX)] + eligible = False + for cap in capabilities: + if cap in can_fix_caps or cap in can_detect_caps: + eligible = True + if eligible: + result.add(bear) + + return result + + +def get_bears_capabilties(bears_by_lang): + """ + Return a dict of capabilties of all the bears by language + in the `bears_by_lang` dictionary. + + :param bears_by_lang: dict with language names as keys + and the list of bears as values. + :returns: dict of capabilities by language. + """ + result = {} + for lang, lang_bears in bears_by_lang.items(): + result[lang] = set() + for bear in lang_bears: + for cap in bear.CAN_FIX | bear.CAN_DETECT: + result[lang].add(cap) + return result + + +def generate_capabilties_map(bears_by_lang): + """ + Generates a dictionary of capabilities, languages and the + corresponding bears from the given ``bears_by_lang`` dict. + + :param bears_by_lang: dict with language names as keys + and the list of bears as values. + :returns: dict of the form + { + "language": { + "detect": [list, of, bears] + "fix": [list, of, bears] + } + } + """ + + def nested_dict(): + return defaultdict(dict) + capabilities_meta = defaultdict(nested_dict) + + # collectiong the capabilities meta-data + for lang, bears in bears_by_lang.items(): + can_detect_meta = inverse_dicts( + *[{bear: list(bear.CAN_DETECT)} for bear in bears]) + can_fix_meta = inverse_dicts( + *[{bear: list(bear.CAN_FIX)} for bear in bears]) + + for capability, bears in can_detect_meta.items(): + capabilities_meta[capability][lang]["DETECT"] = bears + + for capability, bears in can_fix_meta.items(): + capabilities_meta[capability][lang]["FIX"] = bears + return capabilities_meta + + +def remove_bears_with_conflicting_capabilties(bears_by_lang): + """ + Eliminate bears having no unique capabilities among the other + bears present in the list. + Gives preference to: + - The bears already having dependencies installed. + - Bears that can fix the capability rather that just detecting it. + + :param bears_by_lang: dict with language names as keys + and the list of bears as values. + """ + result = {} + for lang, bears in bears_by_lang.items(): + lang_result = set() + capabilities_map = generate_capabilties_map({lang: bears}) + for cap in capabilities_map.keys(): + # bears that can fix the ``cap`` capabilitiy + fix_bears = capabilities_map[cap][lang].get("FIX") + if fix_bears: + for bear in fix_bears: + if bear.check_prerequisites() is True: + # The dependecies for bear are already installed, + # so select it. + lang_result.add(bear) + break + # None of the bear has it's dependency installed, select + # a random bear. + lang_result.add(random.choice(fix_bears)) + break + # There were no bears to fix the capability + detect_bears = capabilities_map[cap][lang].get("DETECT") + if detect_bears: + for bear in detect_bears: + if bear.check_prerequisites() is True: + lang_result.add(bear) + break + lang_result.add(random.choice(detect_bears)) + break + result[lang] = lang_result + return result def is_version_newer(semver1, semver2): """ + Compares version strings and checks if the semver1 is + newer than semver2. :returns: True if semver1 is latest or matches semver2, False otherwise. @@ -263,6 +418,9 @@ def is_version_newer(semver1, semver2): def prompt_to_activate(bear, printer): """ Prompts the user to activate a bear. + + :param bear: The name of the bear. + :param printer: A `ConsolePrinter` object for console interaction. """ PROMPT_TO_ACTIVATE_STR = ("coala-quickstart has found {} to be useful " "based of dependencies discovered from your " diff --git a/tests/generation/Bears.py b/tests/generation/Bears.py index fe4a05d..3e67f3c 100644 --- a/tests/generation/Bears.py +++ b/tests/generation/Bears.py @@ -11,7 +11,6 @@ filter_relevant_bears, print_relevant_bears) from coala_quickstart.coala_quickstart import main from coala_quickstart.coala_quickstart import _get_arg_parser -from coala_quickstart.Constants import IMPORTANT_BEAR_LIST from coala_quickstart.generation.InfoCollector import collect_info from tests.TestUtilities import bear_test_module, generate_files