# Path: src/tidypy/tools/base.py
# class PythonTool(Tool):
#     """
#     A convenience abstract class that automatically sets the ``filters`` in the
#     tool configuration to target Python source files.
#     """
# 
#     # pylint: disable=abstract-method
# 
#     @classmethod
#     def get_default_config(cls):
#         config = Tool.get_default_config()
#         config['filters'] = [
#             r'\.py$',
#         ]
#         return config
# 
# class Issue:
#     """
#     A class that encapsulates an issue found during the analysis of a project.
#     """
# 
#     #: A string containing name of the tool that found the issue.
#     tool = None
# 
#     #: A string containing a code that identifies the type of issue found.
#     code = None
# 
#     #: A string containing a description of the issue.
#     message = None
# 
#     #: A string containing the full path to the file where the issue was found.
#     filename = None
# 
#     #: The line number within the file where the issue was found (if known).
#     #: The first line in a file is notated as 1 (not zero).
#     line = None
# 
#     #: The character number within the line of the file where the issue was
#     #: found (if known). The first column in a line is notated as 1 (not zero).
#     character = None
# 
#     #: A character indicating the comparable pylint category this issue would
#     #: fall into: E=error, W=warning, R=refactor, C=convention
#     pylint_type = 'E'
# 
#     def __init__(
#             self,
#             code=None,
#             message=None,
#             filename=None,
#             line=None,
#             character=None):
#         self.code = code
#         self.message = message
#         self.filename = filename
#         self.line = line if line is not None else 1
#         self.character = character or None
# 
#     def __repr__(self):
#         return '%s(%s)' % (
#             self.__class__.__name__,
#             ', '.join([
#                 repr(self.code),
#                 repr(self.message),
#                 repr(self.filename),
#                 repr(self.line),
#                 repr(self.character),
#             ]),
#         )
# 
# class AccessIssue(TidyPyIssue):
#     """
#     An issue indicating that a file/directory cannot be accessed (typically
#     due to permissions).
#     """
# 
#     def __init__(self, exc, filename):
#         super().__init__(
#             'access',
#             'Cannot access file (%s)' % (exc,),
#             filename,
#         )
# 
# class ParseIssue(TidyPyIssue):
#     """
#     An issue indicating that a file could not be parsed as expected (e.g., a
#     Python source file with invalid syntax).
#     """
# 
#     def __init__(self, exc, filename, line=None, character=None):
#         if isinstance(exc, SyntaxError):
#             line = exc.lineno
#             character = exc.offset
# 
#         super().__init__(
#             'parse',
#             'Unable to parse file (%s)' % (exc,),
#             filename,
#             line,
#             character,
#         )
# 
# Path: src/tidypy/util.py
# def parse_python_file(filepath):
#     """
#     Retrieves the AST of the specified file.
# 
#     This function performs simple caching so that the same file isn't read or
#     parsed more than once per process.
# 
#     :param filepath: the file to parse
#     :type filepath: str
#     :returns: ast.AST
#     """
# 
#     with _AST_CACHE_LOCK:
#         if filepath not in _AST_CACHE:
#             source = read_file(filepath)
#             _AST_CACHE[filepath] = ast.parse(source, filename=filepath)
#     return _AST_CACHE[filepath]


from mccabe import PathGraphingAstVisitor
from .base import PythonTool, Issue, AccessIssue, ParseIssue
from ..util import parse_python_file

CODE = 'complex'
DESCRIPTION = '"{entity}" is too complex ({score})'


class McCabeTool(PythonTool):
    """
    Ned Batchelder's script to check the McCabe the cyclomatic complexity of
    Python code.
    """

    @classmethod
    def get_default_config(cls):
        config = PythonTool.get_default_config()
        config['options'] = {
            'max-complexity': 10,
        }
        return config

    @classmethod
    def get_all_codes(cls):
        return [(CODE, DESCRIPTION)]

    def execute(self, finder):
        issues = []
        if CODE in self.config['disabled']:
            return issues

        for filepath in finder.files(self.config['filters']):
            try:

# Path: src/tidypy/tools/base.py
# class PythonTool(Tool):
#     """
#     A convenience abstract class that automatically sets the ``filters`` in the
#     tool configuration to target Python source files.
#     """
# 
#     # pylint: disable=abstract-method
# 
#     @classmethod
#     def get_default_config(cls):
#         config = Tool.get_default_config()
#         config['filters'] = [
#             r'\.py$',
#         ]
#         return config
# 
# class Issue:
#     """
#     A class that encapsulates an issue found during the analysis of a project.
#     """
# 
#     #: A string containing name of the tool that found the issue.
#     tool = None
# 
#     #: A string containing a code that identifies the type of issue found.
#     code = None
# 
#     #: A string containing a description of the issue.
#     message = None
# 
#     #: A string containing the full path to the file where the issue was found.
#     filename = None
# 
#     #: The line number within the file where the issue was found (if known).
#     #: The first line in a file is notated as 1 (not zero).
#     line = None
# 
#     #: The character number within the line of the file where the issue was
#     #: found (if known). The first column in a line is notated as 1 (not zero).
#     character = None
# 
#     #: A character indicating the comparable pylint category this issue would
#     #: fall into: E=error, W=warning, R=refactor, C=convention
#     pylint_type = 'E'
# 
#     def __init__(
#             self,
#             code=None,
#             message=None,
#             filename=None,
#             line=None,
#             character=None):
#         self.code = code
#         self.message = message
#         self.filename = filename
#         self.line = line if line is not None else 1
#         self.character = character or None
# 
#     def __repr__(self):
#         return '%s(%s)' % (
#             self.__class__.__name__,
#             ', '.join([
#                 repr(self.code),
#                 repr(self.message),
#                 repr(self.filename),
#                 repr(self.line),
#                 repr(self.character),
#             ]),
#         )
# 
# class AccessIssue(TidyPyIssue):
#     """
#     An issue indicating that a file/directory cannot be accessed (typically
#     due to permissions).
#     """
# 
#     def __init__(self, exc, filename):
#         super().__init__(
#             'access',
#             'Cannot access file (%s)' % (exc,),
#             filename,
#         )
# 
# class ParseIssue(TidyPyIssue):
#     """
#     An issue indicating that a file could not be parsed as expected (e.g., a
#     Python source file with invalid syntax).
#     """
# 
#     def __init__(self, exc, filename, line=None, character=None):
#         if isinstance(exc, SyntaxError):
#             line = exc.lineno
#             character = exc.offset
# 
#         super().__init__(
#             'parse',
#             'Unable to parse file (%s)' % (exc,),
#             filename,
#             line,
#             character,
#         )
# 
# Path: src/tidypy/util.py
# def parse_python_file(filepath):
#     """
#     Retrieves the AST of the specified file.
# 
#     This function performs simple caching so that the same file isn't read or
#     parsed more than once per process.
# 
#     :param filepath: the file to parse
#     :type filepath: str
#     :returns: ast.AST
#     """
# 
#     with _AST_CACHE_LOCK:
#         if filepath not in _AST_CACHE:
#             source = read_file(filepath)
#             _AST_CACHE[filepath] = ast.parse(source, filename=filepath)
#     return _AST_CACHE[filepath]


# Path: src/tidypy/tools/mccabe.py
from mccabe import PathGraphingAstVisitor
from .base import PythonTool, Issue, AccessIssue, ParseIssue
from ..util import parse_python_file


CODE = 'complex'
DESCRIPTION = '"{entity}" is too complex ({score})'


class McCabeTool(PythonTool):
    """
    Ned Batchelder's script to check the McCabe the cyclomatic complexity of
    Python code.
    """

    @classmethod
    def get_default_config(cls):
        config = PythonTool.get_default_config()
        config['options'] = {
            'max-complexity': 10,
        }
        return config

    @classmethod
    def get_all_codes(cls):
        return [(CODE, DESCRIPTION)]

    def execute(self, finder):
        issues = []
        if CODE in self.config['disabled']:
            return issues

        for filepath in finder.files(self.config['filters']):
            try:

                tree = parse_python_file(filepath)