ansible-later/env_27/lib/python2.7/site-packages/flake8/main/application.py
2019-04-11 13:00:36 +02:00

408 lines
15 KiB
Python

"""Module containing the application logic for Flake8."""
from __future__ import print_function
import logging
import sys
import time
from typing import List, Optional
import flake8
from flake8 import checker
from flake8 import defaults
from flake8 import exceptions
from flake8 import style_guide
from flake8 import utils
from flake8.main import options
from flake8.options import aggregator, config
from flake8.options import manager
from flake8.plugins import manager as plugin_manager
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
# fmt: off
# `typing.Type` as introduced in 3.5.2
from typing import Type # noqa: F401 (until flake8 3.7)
from flake8.formatting.base import BaseFormatter # noqa: F401, E501 (until flake8 3.7)
# fmt: on
LOG = logging.getLogger(__name__)
class Application(object):
"""Abstract our application into a class."""
def __init__(self, program="flake8", version=flake8.__version__):
# type: (str, str) -> None
"""Initialize our application.
:param str program:
The name of the program/application that we're executing.
:param str version:
The version of the program/application we're executing.
"""
#: The timestamp when the Application instance was instantiated.
self.start_time = time.time()
#: The timestamp when the Application finished reported errors.
self.end_time = None
#: The name of the program being run
self.program = program
#: The version of the program being run
self.version = version
#: The instance of :class:`flake8.options.manager.OptionManager` used
#: to parse and handle the options and arguments passed by the user
self.option_manager = manager.OptionManager(
prog="flake8", version=flake8.__version__
)
options.register_default_options(self.option_manager)
#: The preliminary options parsed from CLI before plugins are loaded,
#: into a :class:`optparse.Values` instance
self.prelim_opts = None
#: The preliminary arguments parsed from CLI before plugins are loaded
self.prelim_args = None
#: The instance of :class:`flake8.options.config.ConfigFileFinder`
self.config_finder = None
#: The :class:`flake8.options.config.LocalPlugins` found in config
self.local_plugins = None
#: The instance of :class:`flake8.plugins.manager.Checkers`
self.check_plugins = None
#: The instance of :class:`flake8.plugins.manager.ReportFormatters`
self.formatting_plugins = None
#: The user-selected formatter from :attr:`formatting_plugins`
self.formatter = None
#: The :class:`flake8.style_guide.StyleGuideManager` built from the
#: user's options
self.guide = None
#: The :class:`flake8.checker.Manager` that will handle running all of
#: the checks selected by the user.
self.file_checker_manager = None
#: The user-supplied options parsed into an instance of
#: :class:`optparse.Values`
self.options = None
#: The left over arguments that were not parsed by
#: :attr:`option_manager`
self.args = None
#: The number of errors, warnings, and other messages after running
#: flake8 and taking into account ignored errors and lines.
self.result_count = 0
#: The total number of errors before accounting for ignored errors and
#: lines.
self.total_result_count = 0
#: Whether or not something catastrophic happened and we should exit
#: with a non-zero status code
self.catastrophic_failure = False
#: Whether the program is processing a diff or not
self.running_against_diff = False
#: The parsed diff information
self.parsed_diff = {}
def parse_preliminary_options_and_args(self, argv=None):
# type: (Optional[List[str]]) -> None
"""Get preliminary options and args from CLI, pre-plugin-loading.
We need to know the values of a few standard options and args now, so
that we can find config files and configure logging.
Since plugins aren't loaded yet, there may be some as-yet-unknown
options; we ignore those for now, they'll be parsed later when we do
real option parsing.
Sets self.prelim_opts and self.prelim_args.
:param list argv:
Command-line arguments passed in directly.
"""
# We haven't found or registered our plugins yet, so let's defer
# printing the version until we aggregate options from config files
# and the command-line. First, let's clone our arguments on the CLI,
# then we'll attempt to remove ``--version`` so that we can avoid
# triggering the "version" action in optparse. If it's not there, we
# do not need to worry and we can continue. If it is, we successfully
# defer printing the version until just a little bit later.
# Similarly we have to defer printing the help text until later.
args = (argv or sys.argv)[:]
try:
args.remove("--version")
except ValueError:
pass
try:
args.remove("--help")
except ValueError:
pass
try:
args.remove("-h")
except ValueError:
pass
opts, args = self.option_manager.parse_known_args(args)
# parse_known_args includes program name and unknown options as args
args = [a for a in args[1:] if not a.startswith("-")]
self.prelim_opts, self.prelim_args = opts, args
def exit(self):
# type: () -> None
"""Handle finalization and exiting the program.
This should be the last thing called on the application instance. It
will check certain options and exit appropriately.
"""
if self.options.count:
print(self.result_count)
if not self.options.exit_zero:
raise SystemExit(
(self.result_count > 0) or self.catastrophic_failure
)
def make_config_finder(self):
"""Make our ConfigFileFinder based on preliminary opts and args."""
if self.config_finder is None:
extra_config_files = utils.normalize_paths(
self.prelim_opts.append_config
)
self.config_finder = config.ConfigFileFinder(
self.option_manager.program_name,
self.prelim_args,
extra_config_files,
)
def find_plugins(self):
# type: () -> None
"""Find and load the plugins for this application.
If :attr:`check_plugins`, or :attr:`formatting_plugins` are ``None``
then this method will update them with the appropriate plugin manager
instance. Given the expense of finding plugins (via :mod:`entrypoints`)
we want this to be idempotent and so only update those attributes if
they are ``None``.
"""
if self.local_plugins is None:
self.local_plugins = config.get_local_plugins(
self.config_finder,
self.prelim_opts.config,
self.prelim_opts.isolated,
)
sys.path.extend(self.local_plugins.paths)
if self.check_plugins is None:
self.check_plugins = plugin_manager.Checkers(
self.local_plugins.extension
)
if self.formatting_plugins is None:
self.formatting_plugins = plugin_manager.ReportFormatters(
self.local_plugins.report
)
self.check_plugins.load_plugins()
self.formatting_plugins.load_plugins()
def register_plugin_options(self):
# type: () -> None
"""Register options provided by plugins to our option manager."""
self.check_plugins.register_options(self.option_manager)
self.check_plugins.register_plugin_versions(self.option_manager)
self.formatting_plugins.register_options(self.option_manager)
def parse_configuration_and_cli(self, argv=None):
# type: (Optional[List[str]]) -> None
"""Parse configuration files and the CLI options.
:param list argv:
Command-line arguments passed in directly.
"""
if self.options is None and self.args is None:
self.options, self.args = aggregator.aggregate_options(
self.option_manager, self.config_finder, argv
)
self.running_against_diff = self.options.diff
if self.running_against_diff:
self.parsed_diff = utils.parse_unified_diff()
if not self.parsed_diff:
self.exit()
self.options._running_from_vcs = False
self.check_plugins.provide_options(
self.option_manager, self.options, self.args
)
self.formatting_plugins.provide_options(
self.option_manager, self.options, self.args
)
def formatter_for(self, formatter_plugin_name):
"""Retrieve the formatter class by plugin name."""
default_formatter = self.formatting_plugins["default"]
formatter_plugin = self.formatting_plugins.get(formatter_plugin_name)
if formatter_plugin is None:
LOG.warning(
'"%s" is an unknown formatter. Falling back to default.',
formatter_plugin_name,
)
formatter_plugin = default_formatter
return formatter_plugin.execute
def make_formatter(self, formatter_class=None):
# type: (Optional[Type[BaseFormatter]]) -> None
"""Initialize a formatter based on the parsed options."""
if self.formatter is None:
format_plugin = self.options.format
if 1 <= self.options.quiet < 2:
format_plugin = "quiet-filename"
elif 2 <= self.options.quiet:
format_plugin = "quiet-nothing"
if formatter_class is None:
formatter_class = self.formatter_for(format_plugin)
self.formatter = formatter_class(self.options)
def make_guide(self):
# type: () -> None
"""Initialize our StyleGuide."""
if self.guide is None:
self.guide = style_guide.StyleGuideManager(
self.options, self.formatter
)
if self.running_against_diff:
self.guide.add_diff_ranges(self.parsed_diff)
def make_file_checker_manager(self):
# type: () -> None
"""Initialize our FileChecker Manager."""
if self.file_checker_manager is None:
self.file_checker_manager = checker.Manager(
style_guide=self.guide,
arguments=self.args,
checker_plugins=self.check_plugins,
)
def run_checks(self, files=None):
# type: (Optional[List[str]]) -> None
"""Run the actual checks with the FileChecker Manager.
This method encapsulates the logic to make a
:class:`~flake8.checker.Manger` instance run the checks it is
managing.
:param list files:
List of filenames to process
"""
if self.running_against_diff:
files = sorted(self.parsed_diff)
self.file_checker_manager.start(files)
try:
self.file_checker_manager.run()
except exceptions.PluginExecutionFailed as plugin_failed:
print(str(plugin_failed))
print("Run flake8 with greater verbosity to see more details")
self.catastrophic_failure = True
LOG.info("Finished running")
self.file_checker_manager.stop()
self.end_time = time.time()
def report_benchmarks(self):
"""Aggregate, calculate, and report benchmarks for this run."""
if not self.options.benchmark:
return
time_elapsed = self.end_time - self.start_time
statistics = [("seconds elapsed", time_elapsed)]
add_statistic = statistics.append
for statistic in defaults.STATISTIC_NAMES + ("files",):
value = self.file_checker_manager.statistics[statistic]
total_description = "total " + statistic + " processed"
add_statistic((total_description, value))
per_second_description = statistic + " processed per second"
add_statistic((per_second_description, int(value / time_elapsed)))
self.formatter.show_benchmarks(statistics)
def report_errors(self):
# type: () -> None
"""Report all the errors found by flake8 3.0.
This also updates the :attr:`result_count` attribute with the total
number of errors, warnings, and other messages found.
"""
LOG.info("Reporting errors")
results = self.file_checker_manager.report()
self.total_result_count, self.result_count = results
LOG.info(
"Found a total of %d violations and reported %d",
self.total_result_count,
self.result_count,
)
def report_statistics(self):
"""Aggregate and report statistics from this run."""
if not self.options.statistics:
return
self.formatter.show_statistics(self.guide.stats)
def initialize(self, argv):
# type: (Optional[List[str]]) -> None
"""Initialize the application to be run.
This finds the plugins, registers their options, and parses the
command-line arguments.
"""
# NOTE(sigmavirus24): When updating this, make sure you also update
# our legacy API calls to these same methods.
self.parse_preliminary_options_and_args(argv)
flake8.configure_logging(
self.prelim_opts.verbose, self.prelim_opts.output_file
)
self.make_config_finder()
self.find_plugins()
self.register_plugin_options()
self.parse_configuration_and_cli(argv)
self.make_formatter()
self.make_guide()
self.make_file_checker_manager()
def report(self):
"""Report errors, statistics, and benchmarks."""
self.formatter.start()
self.report_errors()
self.report_statistics()
self.report_benchmarks()
self.formatter.stop()
def _run(self, argv):
# type: (Optional[List[str]]) -> None
self.initialize(argv)
self.run_checks()
self.report()
def run(self, argv=None):
# type: (Optional[List[str]]) -> None
"""Run our application.
This method will also handle KeyboardInterrupt exceptions for the
entirety of the flake8 application. If it sees a KeyboardInterrupt it
will forcibly clean up the :class:`~flake8.checker.Manager`.
"""
try:
self._run(argv)
except KeyboardInterrupt as exc:
print("... stopped")
LOG.critical("Caught keyboard interrupt from user")
LOG.exception(exc)
self.catastrophic_failure = True
except exceptions.ExecutionError as exc:
print("There was a critical error during execution of Flake8:")
print(exc)
LOG.exception(exc)
self.catastrophic_failure = True
except exceptions.EarlyQuit:
self.catastrophic_failure = True
print("... stopped while processing files")