<commit_before># -*- coding: utf-8
"""Logic to add suggestions to exceptions."""
import keyword
import difflib
import re

#: Standard modules we'll consider while searching for undefined values
# To be completed
STAND_MODULES = set(['string', 'os', 'sys', 're', 'math', 'random',
                     'datetime', 'timeit', 'unittest', 'itertools',
                     'functools'])


def merge_dict(*dicts):
    """Merge dictionnaries.
    It gives priority to the beginning in case of duplicated keys()."""
    ret = dict()
    for dict_ in reversed(dicts):
        ret.update(dict_)
    return ret


def get_objects_in_frame(frame):
    """Get objects defined in a given frame.
    This includes variable, types, builtins, etc."""
    return merge_dict(  # LEGB Rule (missing E atm - not sure if a problem)
        frame.f_locals,
        frame.f_globals,
        frame.f_builtins,
    )


def get_close_matches(word, possibilities):
    """Wrapper around difflib.get_close_matches() to be able to
    change default values or implementation defails easily."""
    return difflib.get_close_matches(word, possibilities)


def get_var_suggestions(var, frame):
    """Get the suggestions closest to the variable names."""
    sugg = []
    objs = get_objects_in_frame(frame)
    for name, obj in objs.items():
        if hasattr(obj, var):
            sugg.append(name + '.' + var)
    if var in STAND_MODULES:
        sugg.append('import ' + var)
    sugg.extend(get_close_matches(
        var,
        list(objs.keys()) + keyword.kwlist))
    return sugg


def get_method_suggestions(type_str, method, frame):
    """Get the suggestions closest to the method name for a given type."""
    sugg = []
    if method in frame.f_builtins:
        sugg.append(method + '(' + type_str + ')')
    # For module, we want to get the actual name of the module
    if type_str == 'module':
        type_str = frame.f_code.co_names[0]  # this probably works
    attributes = set(dir(get_objects_in_frame(frame)[type_str]))

    # Logic for usual containers : add <-> append, etc
    for set_sub in [set(['add', 'append']), set(['extend', 'update'])]:
        if method in set_sub:
            sugg.extend(set_sub & attributes)

    sugg.extend(get_close_matches(
        method,
        attributes))
    return sugg


def import_from_frame(module_name, frame):
    """Wrapper around import to use information from frame."""
    return __import__(module_name, globals=frame.f_globals, locals=frame.f_locals)


def get_imported_suggestion(imported_fail, frame):
    """Get the suggestions closest to the failing import."""
    sugg = []
    module_name = frame.f_code.co_names[0]
    sugg.extend(get_close_matches(
        imported_fail,
        dir(import_from_frame(module_name, frame))))
    for mod in STAND_MODULES:
        if imported_fail in dir(import_from_frame(mod, frame)):
            sugg.append('from %s import %s' % (mod, imported_fail))
    return sugg


def get_suggestion_string(sugg):
    """Return the suggestion list as a string."""
    return ". Did you mean '" + "', '".join(sugg) + "'?" if sugg else ""


def debug_traceback(traceback):
    """Print information from the traceback for debugging purposes."""
    while traceback:
        frame = traceback.tb_frame
        if not traceback.tb_next:
            assert traceback.tb_lineno == frame.f_lineno
            assert traceback.tb_lasti == frame.f_lasti
        print(traceback,
              traceback.tb_lasti,
              frame.f_lasti,
              traceback.tb_lineno,
              frame.f_lineno,
              frame.f_code.co_name,
              frame.f_code.co_names)
        traceback = traceback.tb_next


def add_suggestions_to_exception(type_, value, traceback):
    """Add suggestion to an exception.
    Arguments are such as provided by sys.exc_info()."""
    # We care about the last element of the traceback
    end_traceback = traceback
    while end_traceback.tb_next:
        end_traceback = end_traceback.tb_next
    if issubclass(type_, NameError):
        assert len(value.args) == 1
        if issubclass(type_, UnboundLocalError):
            match = re.match("^local variable '(\w+)' referenced before assignment$", value.args[0])
        else:
            match = re.match("^(?:global )?name '(\w+)' is not defined$", value.args[0])
        assert match, "No match for %s" % value.args[0]
        var, = match.groups()
        sugg = get_var_suggestions(var, end_traceback.tb_frame)
        value.args = (value.args[0] + get_suggestion_string(sugg), )
        assert len(value.args) == 1
    elif issubclass(type_, AttributeError):
        assert len(value.args) == 1
        match = re.match("^'?(\w+)'? (?:object|instance) has no attribute '(\w+)'$", value.args[0])
        assert match, "No match for %s" % value.args[0]
        type_str, method = match.groups()
        sugg = get_method_suggestions(type_str, method, end_traceback.tb_frame)
        value.args = (value.args[0] + get_suggestion_string(sugg), )
        assert len(value.args) == 1
    elif issubclass(type_, TypeError):
        assert len(value.args) == 1
        match = re.match("^'(\w+)' object (?:is (?:not |un)subscriptable|has no attribute '__getitem__')$", value.args[0])
        if match:  # It could be cool to extract relevant info from the trace
            type_str, = match.groups()
            if type_str == 'function':
                value.args = (value.args[0] + get_suggestion_string([type_str + '(value)']), )
        assert len(value.args) == 1
    elif issubclass(type_, ImportError):
        assert len(value.args) == 1
        match = re.match("^No module named '?(\w+)'?$", value.args[0])
        if match:
            module_str, = match.groups()
            sugg = get_close_matches(module_str, STAND_MODULES)
            value.args = (value.args[0] + get_suggestion_string(sugg), )
        else:
            match = re.match("^cannot import name '?(\w+)'?$", value.args[0])
            assert match, "No match for %s" % value.args[0]
            imported_fail, = match.groups()
            sugg = get_imported_suggestion(imported_fail, end_traceback.tb_frame)
            value.args = (value.args[0] + get_suggestion_string(sugg), )
        assert len(value.args) == 1
    else:
        print(type_, value.args)
    # Could be added : IndexError, KeyError<commit_msg>Reorganise code, split into small functions, add documentations<commit_after># -*- coding: utf-8
"""Logic to add suggestions to exceptions."""
import keyword
import difflib
import re

#: Standard modules we'll consider while searching for undefined values
# To be completed
STAND_MODULES = set(['string', 'os', 'sys', 're', 'math', 'random',
                     'datetime', 'timeit', 'unittest', 'itertools',
                     'functools'])

#: Almost synonyms methods that can be confused from one type to another
# To be completed
SYNONYMS_SETS = [set(['add', 'append']), set(['extend', 'update'])]


def merge_dict(*dicts):
    """Merge dictionnaries.
    It gives priority to the beginning in case of duplicated keys()."""
    ret = dict()
    for dict_ in reversed(dicts):
        ret.update(dict_)
    return ret


def get_objects_in_frame(frame):
    """Get objects defined in a given frame.
    This includes variable, types, builtins, etc."""
    return merge_dict(  # LEGB Rule (missing E atm - not sure if a problem)
        frame.f_locals,
        frame.f_globals,
        frame.f_builtins,
    )


def get_close_matches(word, possibilities):
    """Wrapper around difflib.get_close_matches() to be able to
    change default values or implementation defails easily."""
    return difflib.get_close_matches(word, possibilities)


def suggest_name_as_attribute(name, objdict):
    """Suggest that name could be an attribute of an object.
    Example: 'do_stuff()' -> 'self.do_stuff()'."""
    return [nameobj + '.' + name
            for nameobj, obj in objdict.items()
            if hasattr(obj, name)]


def suggest_name_as_standard_module(name):
    """Suggest that name could be a non-importer standard module.
    Example: 'os.whatever' -> 'import os' and then 'os.whatever'."""
    return ['import ' + name] if name in STAND_MODULES else []


def suggest_name_as_name_typo(name, objdict):
    """Suggest that name could be a typo (misspelled existing name).
    Example: 'foobaf' -> 'foobar'."""
    return get_close_matches(name, objdict.keys())


def suggest_name_as_keyword_typo(name):
    """Suggest that name could be a typo (misspelled keyword).
    Example: 'yieldd' -> 'yield'."""
    return get_close_matches(name, keyword.kwlist)


def get_name_suggestions(name, frame):
    """Get the suggestions for name in case of NameError."""
    objs = get_objects_in_frame(frame)
    return suggest_name_as_attribute(name, objs) + \
        suggest_name_as_standard_module(name) + \
        suggest_name_as_name_typo(name, objs) + \
        suggest_name_as_keyword_typo(name)


def suggest_attribute_as_builtin(attribute, type_str, frame):
    """Suggest that a builtin was used as an attribute.
    Example: 'lst.len()' -> 'len(lst)'."""
    return [attribute + '(' + type_str + ')'] \
        if attribute in frame.f_builtins else []


def suggest_attribute_synonyms(attribute, attributes):
    """Suggest that a method with a similar meaning was used.
    Example: 'lst.add(e)' -> 'lst.append(e)'."""
    return [syn
            for set_sub in SYNONYMS_SETS
            if attribute in set_sub
            for syn in set_sub & attributes]


def suggest_attribute_as_typo(attribute, attributes):
    """Suggest the attribute could be a typo.
    Example: 'a.do_baf()' -> 'a.do_bar()'."""
    return get_close_matches(attribute, attributes)


def get_attribute_suggestions(type_str, attribute, frame):
    """Get the suggestions closest to the attribute name for a given type."""
    # For module, we want to get the actual name of the module
    module_name = frame.f_code.co_names[0]
    type_or_module = module_name if type_str == 'module' else type_str
    attributes = set(dir(get_objects_in_frame(frame)[type_or_module]))

    return suggest_attribute_as_builtin(attribute, type_str, frame) + \
        suggest_attribute_synonyms(attribute, attributes) + \
        suggest_attribute_as_typo(attribute, attributes)


def import_from_frame(module_name, frame):
    """Wrapper around import to use information from frame."""
    return __import__(
        module_name,
        globals=frame.f_globals,
        locals=frame.f_locals)


def suggest_imported_name_as_typo(imported_fail, frame):
    """Suggest that imported name could be a typo from actual name in module.
    Example: 'from math import pie' -> 'from math import pi'."""
    module_name = frame.f_code.co_names[0]
    return get_close_matches(
        imported_fail,
        dir(import_from_frame(module_name, frame)))


def suggest_import_from_standard_module(imported_fail, frame):
    """Suggest than name could be found in a standard module.
    Example: 'from itertools import pi' -> 'from math import pi'."""
    return ['from %s import %s' % (mod, imported_fail)
            for mod in STAND_MODULES
            if imported_fail in dir(import_from_frame(mod, frame))]


def get_imported_suggestion(imported_fail, frame):
    """Get the suggestions closest to the failing import."""
    return suggest_imported_name_as_typo(imported_fail, frame) + \
        suggest_import_from_standard_module(imported_fail, frame)


def get_module_name_suggestion(module_str):
    """Get the suggestions closest to the failing module import.
    Example: 'import maths' -> 'import math'."""
    return get_close_matches(module_str, STAND_MODULES)


def get_suggestion_string(sugg):
    """Return the suggestion list as a string."""
    return ". Did you mean '" + "', '".join(sugg) + "'?" if sugg else ""


def debug_traceback(traceback):
    """Print information from the traceback for debugging purposes."""
    while traceback:
        frame = traceback.tb_frame
        if not traceback.tb_next:
            assert traceback.tb_lineno == frame.f_lineno
            assert traceback.tb_lasti == frame.f_lasti
        print(traceback,
              traceback.tb_lasti,
              frame.f_lasti,
              traceback.tb_lineno,
              frame.f_lineno,
              frame.f_code.co_name,
              frame.f_code.co_names)
        traceback = traceback.tb_next


def add_suggestions_to_exception(type_, value, traceback):
    """Add suggestion to an exception.
    Arguments are such as provided by sys.exc_info()."""
    # We care about the last element of the traceback
    end_traceback = traceback
    while end_traceback.tb_next:
        end_traceback = end_traceback.tb_next
    if issubclass(type_, NameError):
        assert len(value.args) == 1
        error_msg = value.args[0]
        if issubclass(type_, UnboundLocalError):
            match = re.match("^local variable '(\w+)' referenced before assignment$", error_msg)
        else:
            match = re.match("^(?:global )?name '(\w+)' is not defined$", error_msg)
        assert match, "No match for %s" % error_msg
        var, = match.groups()
        sugg = get_name_suggestions(var, end_traceback.tb_frame)
        value.args = (error_msg + get_suggestion_string(sugg), )
        assert len(value.args) == 1
    elif issubclass(type_, AttributeError):
        assert len(value.args) == 1
        error_msg = value.args[0]
        match = re.match("^'?(\w+)'? (?:object|instance) has no attribute '(\w+)'$", error_msg)
        assert match, "No match for %s" % error_msg
        type_str, attr = match.groups()
        sugg = get_attribute_suggestions(type_str, attr, end_traceback.tb_frame)
        value.args = (error_msg + get_suggestion_string(sugg), )
        assert len(value.args) == 1
    elif issubclass(type_, TypeError):
        assert len(value.args) == 1
        error_msg = value.args[0]
        match = re.match("^'(\w+)' object (?:is (?:not |un)subscriptable|has no attribute '__getitem__')$", error_msg)
        if match:  # It could be cool to extract relevant info from the trace
            type_str, = match.groups()
            if type_str == 'function':
                value.args = (error_msg + get_suggestion_string([type_str + '(value)']), )
        assert len(value.args) == 1
    elif issubclass(type_, ImportError):
        assert len(value.args) == 1
        error_msg = value.args[0]
        match = re.match("^No module named '?(\w+)'?$", error_msg)
        if match:
            module_str, = match.groups()
            sugg = get_module_name_suggestion(module_str)
            value.args = (error_msg + get_suggestion_string(sugg), )
        else:
            match = re.match("^cannot import name '?(\w+)'?$", error_msg)
            assert match, "No match for %s" % error_msg
            imported_fail, = match.groups()
            sugg = get_imported_suggestion(imported_fail, end_traceback.tb_frame)
            value.args = (error_msg + get_suggestion_string(sugg), )
        assert len(value.args) == 1
    else:
        print(type_, value.args)
    # Could be added : IndexError, KeyError
{'fraction_non_alphanumeric': 0.11635883905013193, 'fraction_numerical': 0.002638522427440633, 'mean_word_length': 2.9771773347324237, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5490402', 'n_tokens_mistral': 4690, 'n_tokens_neox': 4596, 'n_words': 1272}